Rails7 システムテスト

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::ResponseAssertionsActionDispatch::Assertions::RoutingAssertionsは、 システムテストではインクルードされないので、以下のアサーションは使えない。
    • assert_response
    • assert_redirect_to
    • assert_generates
    • assert_recognizes
    • assert_routing
  • ActionDispatch::SystemTestCaseCapybara::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_linkclick_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は終わりになります。 次回以降は別のトピックを取り上げたいと思います。

2023

単項マイナスと構文解析

1 minute read

単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合

Raccライブラリと構文解析

3 minute read

パーサ・ジェネレータとは 少し複雑な文法 四則(加減乗除)計算のBNF Racc で実装 クラス定義、BNFの記述部分 ヘッダー、インナー、フッター コンパイルと実行 演算子の優先順位と結合における左右の優先順位 まとめ

StrScanライブラリと字句解析

less than 1 minute read

StrScanライブラリのドキュメント 字句解析とは StrScanライブラリ StrScanライブラリを使った字句解析 実例

Gem

1 minute read

lbtというgemを作って公開してみた lbtはどんなgemか ファイルの配置 lbt.gemspec Rakefile gemのビルド RubyGems.orgへのアップロード 補足・・rake/gempackagetaskサブライブラリについて

Encoding

1 minute read

文字列のエンコーディングに頭を悩ませることはほとんどなくなりました。 なぜなら、どのアプリ、システムもUTF-8を使うようになったからです。 Rubyでもエンコーディングの問題が起こることはまず無いでしょう。 ですが、今回はエンコーディングの考え方を整理してみたいと思います。

Thread

less than 1 minute read

Fiberを書いたときから、次はスレッドを書こうと思っていましたが、時間がかかってしまいました。 その理由は、期待したとおりのスレッドの効果がなかったためです。 今回はそのことを書きますが、これはRubyのスレッドの抱えている問題なのか、自分のやり方が悪いのかははっきりしていません。

Fiber

1 minute read

Fiberは「ノンプリエンプティブな軽量スレッド」とRubyのマニュアルに記載されています。

RDoc

less than 1 minute read

今回はRubyプログラムから自動的にドキュメントを作成するRDocについて書きたいと思います。 私はこのことについて、エキスパートではありません。 この記事も、初心者の体験談だと考えてください。

Back to Top ↑

2022

Ruby/GTK4

5 minute read

Ruby/Gtkの記事を先日書いたときに、「これはかなり使える」という手応えを感じたので、WordBook(Railsで作った単語帳プログラム)のGTK 4版を作りました。 プログラムは「徒然なるままにRuby」のGitHubレポジトリに置いてあります。 レポジトリをダウンロードし、ディレクトリ_example/...

Shoes – Rubyとグラフィック

5 minute read

Rubyはグラフィックについて弱い印象があります。 しかし、グラフィックはデバイスに関することなので、言語そのものには直接の関係はないはずで、あるとすればライブラリです。 今後グラフィック関係のgemが開発されることに期待しましょう。

Rails7 テスト

5 minute read

前回作ったWordbook(リソースフル)のテストを書いてみます。 RailsのテストはminitestをRails用に拡張したものです。

Rails7 モデルとデータベース

5 minute read

今回はRailsにおけるデータの作成と保存、そして変更について説明します。 そのベースになるモデルとデータベースの話から始め、appendとchangeの動作について詳しく説明します。

Rails7とBootstrap

3 minute read

一般に、HTMLは文書の構造を表し、CSSはその体裁(見栄え)を表します。 Railsは最終的にCSSを含むHTML文書を出力するので、この2つについての理解が必須です。 この記事ではとくにCSSの人気ライブラリであるBootstrapを紹介します。 BootstrapはJavascriptも含んでいます。

Rails7のインストール

2 minute read

Rubyの最も人気のあるアプリケーションであるRuby on Railsを取り上げようと思い、書き始めました。 予想してはいましたが、相当な分量になってしまいました。 そのため、何回かに分けて記事にすることにします。 また、対象となる読者のレベルをどうしようかと考えましたが、「徒然Ruby」が基礎的な内容から始ま...

GemとBundler

1 minute read

Rubyのライブラリ管理システムのRubygemsとコマンドgemおよびbundlerについて説明します。

minitest(3)モックの詳細

1 minute read

minitestについて連続して2回書いてきました。 「minitestはドキュメントが少ない」という人がいますが、私も同感です。 例えば、モックとスタブの説明も少ないです。 そこで、今回はmock.rbのソースコードを参考に、モックの私的ドキュメントを書いてみました。 あくまで私個人の考えであり、minites...

minitest(1)テストとは

2 minute read

アプリ作成の記事でminitestを使いました。 今回はminitestについて、また一般にテストについて、私の考えを書こうと思います。

public、private、protected

2 minute read

今回はメソッドの呼び出し制限ついて説明します。 呼び出し制限にはpublic、private、protectedの3つがあります。

アプリ制作、インストール、テスト

1 minute read

2023/10/29 追記:この記事は新しく書き直しました。 古い記事で使っていたGitHubのCalcが大幅にアップデートされたためです。 そこで、この記事に合うようなプログラムsimple_calcを新たに作りました。 このプログラムは本レポジトリの_example/simple_calcにあります。

case文

2 minute read

if〜elsif〜・・・〜else〜endは皆さん良く使うでしょうか? これは場合分けで良く使われる方法です。 これと同様の制御構造にcase文があります。 Cのswitch文に似ていますが、より強力な機能を持っています。 if-else-endよりも高い能力があるといえます。

Lambda

2 minute read

Procオブジェクトを生成するメソッドlambdaについて説明します。

Proc オブジェクト

2 minute read

今回はブロックを一般化したオブジェクトProcを説明します。

モジュール

1 minute read

モジュールには名前空間とミックスイン(Mix-in)の2つの機能があります。 ここではミックスインについて説明します。

Kernelモジュール

less than 1 minute read

Kernelモジュールのメソッドはどこでも使うことができます。 そのメソッドの中には便利で有用なものが多いです。

便利なメソッド

1 minute read

ここでは私が便利だと思ったメソッドを紹介します。

文字列と正規表現

3 minute read

文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。

配列

2 minute read

配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。

トップレベルのメソッド

1 minute read

今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。

ブロックとイテレータ

less than 1 minute read

ブロックはRubyの特長です。 ブロックのおかげで記述が非常にすっきりと分かりやすくなります。 今回はブロックをイテレータの本体として使う方法を説明します。

整数

less than 1 minute read

ここではRubyの最も基本的なオブジェクトである整数について説明します。

Hello world

less than 1 minute read

「徒然なるままに」をネットで調べてみると、「することもなく、手持無沙汰なのにまかせてという意味」とありました。 まさに、自分の現状を言い当てた言葉。 しかも、ブログに書くネタもなかなか思いつかない日々。

Back to Top ↑