Rails7におけるシステムテストについて書きます。
テンプレートの作成
コマンドラインからシステムテストのテンプレートを作成します。
$ bin/rails generate system_test words invoke test_unit create test/system/words_test.rb $
/test/system/words_test.rb
がシステムテストを記述するファイルです。
ファイルを開くとWordsTestクラスの定義があります。
テストはその中に書いていきます。
require "application_system_test_case" class WordsTest < ApplicationSystemTestCase ... ... ... ... ... ... end
アサーション
クラスの親子関係は次のようになります。
WordsTest < ApplicationSystemTestCase < ActionDispatch::SystemTestCase < ActiveSupport::TestCase
このことから、アサーションについて次のことがわかります。
ActiveSupport::TestCase
がインクルードしたアサーションはシステムテストでも使うことができる(以前の3つのテストと同様)assert_changes
assert_difference
assert_no_changes
assert_no_difference
assert_not
assert_nothing_raised
- 機能テストや結合テストでは
ActionDispatch::IntegrationTest
がスーパークラスになっていたが、システムテストではスーパークラスではない。ActionDispatch::IntegrationTest
がインクルードしていたActionDispatch::Assertions::ResponseAssertions
とActionDispatch::Assertions::RoutingAssertions
は、 システムテストではインクルードされないので、以下のアサーションは使えない。assert_response
assert_redirect_to
assert_generates
assert_recognizes
assert_routing
ActionDispatch::SystemTestCase
がCapybara::Minitest::Assertions
をインクルードしているので、Capybaraのアサーションを使うことができる。 このアサーションはRailsのAPIリファランスには説明がない。 CapybaraのAPIリファランスにその説明がある。 よく使いそうなアサーション(個人の独断による)を以下に示す。assert_selector(セレクタ名, オプション)
: セレクタが送られてきたHTMLページ(以下ページと略記)にあるかどうかをチェック。 オプションでよく使うのは「text: 文字列または正規表現」「count: 整数」(そのセレクタの出現回数のチェック)「visible: (true|falseなど)」(可視かどうか)など。assert_text(文字列, オプション)
: 文字列がページにあるかどうかをチェック。 オプションには「count: 整数」などが可。 デフォルトでは文字列は「可視文字列」、つまり改行やコントロールコードなどは含まないassert_button(文字列)
: 引数がボタンのidまたは表示文字列であるようなボタンがあるかどうかのチェックassert_field(文字列, オプション)
: 入力フィールドで引数の文字列にそのラベルまたは名前またはidが一致するものがあるかどうかをチェック。 オプションには「type: "textarea"」などの入力タイプの指定ができるassert_link(文字列, オプション)
: リンクで、そのidまたはテキストが引数の文字列に一致するものがあるかどうかをチェック。 オプションにはリンク先「href: 文字列または正規表現」を指定できるassert_table(文字列, オプション)
: 引数の文字列に一致するidまたはキャプションを持つ表があるかどうかをチェック。 オプションでは表の内容のチェックができるが、詳しくはAPIリファランスを参照してほしいassert_title(文字列)
: 文字列に一致するタイトルを持っているかどうかをチェック
良く使われるメソッド
結合テストで使えたgetメソッドなどはシステムテストでは使えません。 代わりにCapybaraのメソッドを使います。 良く使われると思われるメソッドを以下に簡単に説明します。 その詳細や、他のメソッドについてはCapybaraのドキュメントを参照してください。
visit(URLまたはパス)
: URLへのGETメソッドでのアクセスをシミュレートfill_in(フィールド名, with: 文字列)
: フォーム・フィールドに文字列をセットするclick_link(テキスト)
: リンクのクリックをシミュレート。テキストはそのaタグのコンテンツ(開始タグと終了タグで挟まれた部分)click_button(テキスト)
: ボタンのクリックをシミュレート。テキストはそのボタンに表示される文字列click_on(テキスト)
: リンクまたはボタンのクリックをシミュレート。click_link
とclick_button
の両方をまとめたメソッドaccept_confirm ブロック
: ブロック(多くはリンクやボタンのクリック)を実行したときに、確認ダイアログが現れる場合、確認を受け入れる(OKをクリックなど)dismiss_confirm ブロック
: ブロック(多くはリンクやボタンのクリック)を実行したときに、確認ダイアログが現れる場合、確認を拒否する(NOやCancelをクリックなど)
最後の2つは、例えば削除に対する「are you sure?」などの確認ダイアログが出る場合に必要なメソッドです。
click_on
だけではエラーになります。
ここまでで説明したメソッドは、ユーザがクリックやキーボード入力するのをシミュレートします。 テストでは、ユーザの行動をシミュレートしますので、現実の運用に近い形でのテストになります。
システムテストの例
ここでは、ルートにアクセスしてから、検索、追加、変更、削除を一通り行うことをシミュレートしてテストします。 プログラムリストにコメントしてありますが、次の順に推移することを想定しています。
- ルートにアクセス
- 正規表現「.」で検索
- 単語を追加
- その単語を変更
- その単語を削除
- index画面に戻る
require "application_system_test_case" class WordsTest < ApplicationSystemTestCase test "flow from the root through every action" do # ルートにアクセス=>indexアクションへ visit root_url assert_selector "h1", text: "単語帳" # searchアクションへ fill_in "search", with: "." click_button "検索" assert_selector "h1", text: "単語検索結果" ["tall","高い","house","家"].each do |s| assert_text s end # newアクションへ click_link "追加" fill_in "word[en]", with: "stop" fill_in "word[jp]", with: "止まる" fill_in "word[note]", with: "The machine stopped.\n機械が止まった。\n" wc = Word.count # createアクションへ click_button "作成" # showアクションへリダイレクト assert_text "単語を保存しました" assert_equal 1, Word.count-wc ["stop","止まる","The machine stopped.","機械が止まった。"].each do |s| assert_text s end # editアクションへ click_link "変更" fill_in "word[jp]", with: "止める" fill_in "word[note]", with: "I stopped speaking.\n私は話すのをやめた。\n" # updateアクションへ click_button "変更" # showアクションへリダイレクト ["stop","止める","I stopped speaking.","私は話すのをやめた。"].each do |s| assert_text s end wc = Word.count # deleteアクションへ accept_confirm do click_link "削除" end # indexアクションに戻る assert_text "単語を削除しました" assert_equal -1, Word.count-wc assert_selector "h1", text: "単語帳" end end
プログラムを読んでもらえば、ユーザがクリックしたりキーボード入力するのをそのままシミュレートしていることが分かると思います。 いくつかポイントになる点を書きます。
assert_text
では可視文字列のみをチェックする(デフォルト動作)ので、改行をチェックすることはできません。
2行にわたる文字列は、2回に分けて1行ずつチェックします。
createアクションでデータベースの単語数が増えるのをassert_equal
でチェックしました。
assert_difference
でも可能ですが、そのブロック内にデータベースに書き込むタイミングを含まなければならないことに注意が必要です。
次のプログラムではフェイルする可能性が高いです。
assert_difference("Word.count") do click_button "作成" end assert_text "単語を保存しました"
これは、ボタンクリック直後ではまだデータベースの書き込みが終わっていないため、Word.countがブロックの前後で変化していないためです。 これは次のように書くことで解決できます。
assert_difference("Word.count") do click_button "作成" assert_text "単語を保存しました" end
assert_text
が画面が変わるまで待ってからチェックをしてくれるので、ブロック実行中にデータベース書き込みは行われています。
Capybaraでは、このようにアサーションが画面遷移を待つようになっているので、それに注意が必要です。
assert_difference
を使うことは可能ですが、どちらかといえばassert_equal
を使うほうが分かりやすいように思います。
削除メニューをクリックすると確認ダイアログが現れます。
Capybaraではこのようにダイアログが現れる場合はaccept_conferm
のブロックにリンクやボタンのクリックを書かなければなりません。
そうでないとエラーになります。
もし、ダイアログでCancelやNoボタンを押すことをシミュレートしたい場合はdismiss_confirm
を使います。
テストの実行
テストの実行にはtest:system
を引数にbin/rails
を実行します。
$ bin/rails test:system yarn install v1.22.19 [1/4] Resolving packages... success Already up-to-date. Done in 0.11s. yarn run v1.22.19 $ esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets app/assets/builds/application.js 394.1kb app/assets/builds/application.js.map 721.7kb Done in 0.20s. yarn install v1.22.19 [1/4] Resolving packages... success Already up-to-date. Done in 0.10s. yarn run v1.22.19 $ sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules Done in 1.86s. Running 1 tests in a single process (parallelization threshold is 50) Run options: --seed 20243 # Running: DEBUGGER: Attaching after process 8081 fork to child process 8111 Capybara starting Puma... * Version 5.6.5 , codename: Birdie's Version * Min threads: 0, max threads: 4 * Listening on http://127.0.0.1:38767 . Finished in 3.190794s, 0.3134 runs/s, 5.9546 assertions/s. 1 runs, 19 assertions, 0 failures, 0 errors, 0 skips
多くのメッセージが出ていますが、その多くはテストの準備作業です。 テスト自体は下から14行目の「Running 1 tests in a single process (parallelization threshold is 50)」以下になります。 下から4行目のドットがテストの成功を示しています。 さらに、その詳細は、最終行のテスト1、アサーション19、フェイルとエラーは0だったことが分かります。
ヘッドレスブラウザ
システムテストではブラウザ画面が表示されます。 これは画面を見ることができて良いともいえますが、逆に煩わしいともいえます。 画面表示の部分を無くしたヘッドレスブラウザを使うことでこの問題を解決できます。
設定についてはRails Guideを参照してください。
まとめ
この記事の例から分かると思いますが、システムテストでは実際の運用に近い形でのテストができます。 本当にシミュレーションという感じです。 演劇でいえば舞台稽古、コンサートでいえばリハーサルのようなものです。
したがって、Railsのウェブアプリケーションは最終的にはシステムテストを書かなければならないと思います。 そうであれば、結合テストは省略される可能性もあるでしょう。 これはそれぞれのプロジェクトでの考え方になると思います。
今回でRailsの記事が7本になりました。 ここでRailsは終わりになります。 次回以降は別のトピックを取り上げたいと思います。