RSpec入門・演習3
2023.10.18
今回はsystem specなるものについて学ぶ。まずはインプット。
よし、実践に入る。まずはsystem specの準備。
- Gemのインストール
- formatの設定
- webdriverの設定
準備OK。テストケースの作成に移る。
system specファイルの生成コードまちがってるな。rails g rspec:system ファイル名
が正解っぽい。
まずは一番ケース数の少ないUserSessionsからいこう。記載されてるのは以下の通り。
- ログイン前
- フォームの入力値が正常 => ログイン処理が成功する
- フォームの値が未入力 => ログイン失敗
- ログイン後
- ログアウトボタンクリックする => ログアウトされる
上を更に分解する。ログイン後はそのまんまでいいかな。
ログイン後
- ログアウトボタンをクリックするとログアウトされるか
ログイン前
- ログイン処理失敗
- メールアドレス未入力でログインに失敗するか
- パスワード未入力でログインに失敗するか
- ログイン処理成功
- メールアドレス・パスワード・パスワード確認が全て正しく入力されるとログインに成功するか
続いてUserのテストケース。記載は以下の通り。
ログイン前
- ユーザー新規登録
- フォームの入力値が正常 => 登録成功
- メールアドレスが未入力 => 登録失敗
- 登録済みのメールアドレスを入力する => 登録失敗
- マイページ遷移
- ログイン前 => アクセス失敗
ログイン後
- ユーザー編集
- フォームの入力値が正常 => 編集成功
- メールアドレスが未入力 => 編集失敗
- 登録済みのメールアドレスを入力する => 編集失敗
- 自分以外のユーザー編集ページに遷移する => アクセス失敗
- マイページ遷移
- タスクを作成 => 作成したタスクが表示される
↑をさらに分解。
ログイン前
- ユーザー新規登録
- 失敗
- メールアドレス未入力で登録失敗するか
- 登録済のメールアドレス入力で登録失敗するか
- 成功
- フォームの値が正常で登録成功するか
- 失敗
- マイページ遷移
- 未ログイン状態でマイページにアクセスすると失敗するか
ログイン後
- ユーザー編集
- 編集失敗
- メールアドレス未入力で編集失敗するか
- 登録済のメールアドレス入力で編集失敗するか
- 編集成功
- フォームの入力値が正常で編集成功するか
- 自分以外のユーザー編集ページにアクセスしようとすると失敗するか
- 編集失敗
- マイページ遷移
- タスクを作成するとそのタスクが表示されるか
最後にTaskのケース。記載は次の通り。
ログイン前
- ページ遷移確認
- タスクの新規登録ページにアクセス => アクセス失敗
- タスクの編集ページにアクセス => アクセス失敗
- タスクの詳細ページにアクセス => タスク詳細情報が表示される
- タスク一覧ページにアクセス => タスク一覧が表示される
ログイン後
- タスクの新規登録
- フォームの入力値が正常 => 登録成功
- タイトルが未入力 => 登録失敗
- 登録済みのタイトルを入力する => 登録失敗
- タスクの編集
- フォームの入力値が正常 => 編集成功
- タイトルが未入力 => 編集失敗
- 登録済みのタイトルを入力する => 編集失敗
- タスクの削除
- タスク削除する => 削除成功
本日はここまで。明日はTaskのケース記載を続きからやっていく。
2023.10.19
引き続きTaskのテストケースを記載していく。
うーんなんかやっぱりdescribe
とcontext
の使い方を間違えている気がする…describe
はあくまでテスト対象が何かを書いて、テストの分類はcontext
に任せたほうがいいのかな。
ただそれだと階層が足りない…どうしようかな。
Rails基礎のspecファイルを確認してみたら、describe
の中に更にdescribe
を入れ子にしていいんだ!というかcontext
の中にdescribe
が入ったりもしてる。
やっぱりdescribe
は「テスト対象はなにか(このモデル、このメソッド、あるいはこの機能等)」、context
は「想定している状況、条件」、it
は「成否の判定条件」を書くっていうルールっぽいな。
それに合わせてuserも直そう。
OK。タスクのテストケース記載およびユーザーやユーザーセッションのテストケースの修正完了。
テストコードの記載を始める。
推奨されている、user_session→user→taskの順(難易度低い順)でいく。
rails g
で生成したspecファイルにデフォルトで記述されているdriven_by
は使うブラウザを指定するもの。今回は設定ファイルで一括指定してあるので消してしまってOK。
user_session
ログインするためにはユーザー登録必須なので、beforeでユーザーを生成しておく
... と思ったけど、Rails基礎のspecコード覗いた感じ、変数の宣言はわざわざbefore
の中に入れなくても普通にベタで書いていいっぽいな。そうしよう。
とりあえず書けたのでテスト走らせてみる。
んん〜??謎のエラーが出た。
1) ログイン・ログアウト ログイン前 フォームの入力値が正常の場合 ログインに成功するか
Got 0 failures and 2 other errors:
1.1) Failure/Error: options = Selenium::Webdriver::Chrome::Options.new
NameError:
uninitialized constant Selenium::Webdriver
options = Selenium::Webdriver::Chrome::Options.new
^^^^^^^^^^^
Did you mean? Webdrivers
Selenium::WebDriver
あ、タイポかな?ウェブドライバーの表記を間違えてる疑惑。
当たり。WebDriver
がWebdriver
になってた。再度テスト。
お、無事テストが走った。エラーが以下の通り出た。
1) ログイン・ログアウト ログイン前 フォームの入力値が正常の場合 ログインに成功するか
Failure/Error: Capybara.assert_current_path(root_path, ignore_query: true)
Capybara::ExpectationNotMet:
expected "/login" to equal "/"
(中略)
2) ログイン・ログアウト ログイン後 ログアウトボタンをクリックするとログアウトされるか
Failure/Error: click_button 'Logout'
Capybara::ElementNotFound:
Unable to find button "Logout" that is not disabled
1はおそらくフォームのラベル名が違ってるんじゃないかな。だから入力するべきフォームが見つからずにログイン失敗して/login
が再表示されてる。
2はひょっとするとLogout
がボタンじゃないのかも。
それぞれ確認する。まずは1のスクリーンショット見てみよう。そうすればラベル名がわかる。
2は当たりっぽい。ボタンじゃなくてリンクだった。
1は予想が外れた。ラベル名あってそう。う〜んなんでだろう。
とりあえず2を直して再テストしてみる。
あれー?"Logout"っていうリンクもボタンもないよって言われた。なんで??
…と思ったけどわかった。そもそもログインの処理書いてない!書く。
ついでによく使いそうなのでモジュール化しておくことにする。
ん〜〜やっぱりだめだ。ということはログイン処理がうまくできてないってことだな。どこがまずいんだろう。
let(:user)
じゃなくてlet!(:user)
にしないといけないとか?やってみよう。
違うっぽい…。
今日はここまでにして明日ログイン失敗の謎を解明する。
2023.10.20
今日こそログインできない原因を突き止めたい。
変数宣言使わずに書いてみるか。
ダメだった。
DBスキーマファイルでusersテーブルの確認をしてみて思ったのが、passwordっていうカラムがないからでは?ということ。sorceryを使っているため、crypted_passwordになってる。
あとRails基礎の/spec/rails_helper.rbを見てたらconfig.include Sorcery::TestHelpers::Rails::Request, type: :request
という記述があったので、それを追記してみる。
関係なかった。
どこがうまく行ってないのか探るため、わざとclick_button 'Login'
を消してみた。そしたらスクリーンショットによりどうもパスワードが入力されていないことが判明。なんで??
あ、ひょっとしてさっきのが当たってる?user.password
だとpasswordっていうカラムがないからダメで、文字列としてパスワードを入力しないといけないのでは??
当たり〜〜〜〜!!
うわ〜盲点だった!でも気づいてみれば確かにそうだ!パスワードは通常暗号化された状態で保存されてて、ユーザーが入力したまんまの文字列はDB上にはないから、DB参照しないで直接文字列として指定しなきゃダメなんだ。あ〜〜なんて簡単なことに時間を費やしたんだ〜〜〜〜😭
これから気をつけよう…
ともあれこれでテスト通ったので、userの方に移る。
user
今気づいたけど、「マイページ」ってどのページのことを指してるんだ??コードとかファイル構成見てもいまいちはっきりしないので、とりあえず/users/index.html.erb
ということにしておこう。
へー、redirect_to @user
で自動的にredirect_to user_path(@user)
と同じだと解釈してくれるんだ。知らなかった。
う〜ん、今度はこんなエラーが出ている。
An error occurred while loading ./spec/system/users_spec.rb.
Failure/Error: __send__(method, file)
SyntaxError:
/sample_app_for_rspec/spec/system/users_spec.rb:98: syntax error, unexpected end-of-input, expecting `end'
本来あるべきend
がないよって言われてるっぽいけど、該当箇所見ても問題ないように見える…。
こういう人間だと見つけづらい凡ミス系(だと思う、たぶん。)こそロボらんてくんに助けてほしいんだけど、字数制限でエラーになっちゃうんだよね〜。
今日はもう時間なのでおしまいにして、あした改めてどこがまずいのか探そう。
あ、なるほど。この場合実際にend
無い問題が起こってるのは98行目とは限らないのか。
てことはどっか途中が抜けてるのかも。
どっちにしろ今日はもうここまでにしよう、今度こそ。
と思ったら見つけちゃった〜!直したのでこれでもう一回テスト走らせてみよう。
結局いろんなタイポが見つかって直した。今度こそほんとのほんとにこれでおしまい!続きは明日!
2023.10.21
引き続きuserのテストコードを書いていく。
あれ?いつの間にかユーザ登録失敗パターンがうまく通らなくなってるな。登録ページ(new_user_path)が再表示されずにユーザー一覧(users_path)に行っちゃってるっぽい。
仮説1:ユーザーオブジェクトを引数として渡してないのが問題
user_without_email
とuser_duplicated_email
というユーザーオブジェクトを作ってそれを呼び出す形にしてみる。
特に変わらず。
なのでブラウザからユーザー登録して確かめてみた。すると確かに失敗した場合のパスが/users
になってる。でも表示されてるページはあってる。
ここでrails routes
でルーティングを確認し、そういえばusers#create
がPOSTの/users
だったことを思い出す。
つまり、これで合ってる。ユーザー一覧に飛んでるんじゃなく、usersコントローラのcreateアクションが呼び出されてるだけ。
SignUp
ボタンを押してusers#create
=POSTメソッドの/users
に飛ぶ。登録失敗なのでページの中身だけがusers#new
に置き換わる。render
なのでnew_user_path
に飛ぶわけじゃなく、パスはusers_path
(users#create
)のまま変わらない。
めっちゃ初歩的なことだった今回も…情けない。
とにかく進めよう…ユーザー編集失敗パターンからだな。
Rails基礎のテストコード見てると、どうも失敗時はパスのチェックしてないっぽいな、エラーメッセージのチェックだけで。やっぱりrender
使うことが多いからかな。
同じ感じにしよう。
今更ながらMyPage発見。ユーザー詳細(自分の)みたいだ。直しておこう。
よし、ユーザー編集関連終わり!あとはタスク作成だけだ。
datetimeフィールドに指定の値を入力するのってどうやるんだろう。
fill_in
でいけるっぽいな。
よし、userもできた!最後にtaskだ。
task
Deadlineで手こずったので後でまとめる。
2023.10.22
引き続きtaskのテストコードを書いていく。できれば今日中に終わらせたい。
???
なんで新規登録のときはTime.new(...)
だと通るのにTime.new(...).strftime(...)
だと通らないんだ??
datetime
型は日時だけじゃなく曜日も含むらしい。そこに原因があるのか?
でもだとしたら同じ値を入れてる編集の方はなんで通るんだって話になるけど…。とりあえず試してみるか。
いや、↑のはデータベースのデータ型じゃなくてRubyのクラスの話か。データ型としてのdatetime
はやっぱり日時であってそう。ん〜〜直接文字列で指定してみるか。
お、編集の方を直指定にしたらいけた。新規登録の方も試してみよう。
ええ…ダメじゃん…コピペしたのにどうしてこっちだけダメなの……
スクショよく見るとyearの値がおかしなことになってるな。なんで??
タイムゾーンの問題とかそういうことある??
ちがうっぽい…。
今度は全部1.week.from_now
で統一してみたけどそうするとやっぱり編集の方だけ失敗する。いやマジでなにこれ??同じフォームレンダリングして使ってるんだけど???
TimeWithZone
というRailsのクラスを知ったのでそれを試してみる。Time.zone.now
でどうなるか。
やっぱり編集の方だけ引っかかる。何が違うんだ?新規登録と編集で同じフォームをつかってるはずなのに明らかに何かが違ってる。
ロボらんてくんにも相談してみたけどダメだ…もういいやDeadlineは入力必須じゃないから入力しないことにする。あとで解答例みて答え合わせする。めっちゃ悔しいけどここで詰まっててもしょうがないわ…進むことにする。
全部書けて全部通ったーーー。
まあ加筆修正山程必要だろうけど、いつまでもやっててもしょうがないしここまでとする。解答例を見て正しいやり方を学ぼう。
ここから解答例見て修正
include LoginMacros
はrails_helperに書いちゃうのか。
せっかくだから修正内容をIssueにしよ〜
↓
というか最初からIssue立てて進めればよかったな
↓
あれ?Issueのタブがない
↓
どうやらforkしたリポジトリはデフォルトでIssueが無効になっていて、設定から有効にできるらしい
というわけで今更ながらIssue有効にした。修正内容をここに放り込んでいく。
Capybara.assert_current_path
は要らなかった感じかな全体的に。
登録済メールアドレス云々のケースでわざわざexisted_user
を作るのは、そのほうがコード読んだときにわかりやすいからかな。user.email
でも普通にいけちゃうけど。
have_field
なんていうマッチャーがあるんだな。fill_in
と同じく'フィールド名', with: 値
で書ける。
登録・編集失敗した際に入力した値が再表示される場合はこれも書いたほうがいいな。
create_list
なんてのもあるのか。
create_list(:ファクトリー名, 数)
でまとめてオブジェクトを生成できるんだな。
あーやっぱりというか、新規登録以外Deadlineの入力してないね…。ロボらんてくんもブラウザによってはうまく行かないことがあるって言ってたし、Capybaraのテストだとうまくいかないことがあるって最初からわかってるやつなのかもなあ。
こんなことならもっと早く諦めちゃえばよかったなあ……
「他のユーザーのタスク編集ページにアクセスすると失敗する」は課題のリストにないな。漏れてるのか。
よーーやく完成!コミットしてプルリクする。
OK、ひとまず終了。TILに少しまとめようかな。