お題:こんなとき、どんなテストコードを書く?
以下で示すような質問一覧画面の中から、
「どのエディターを使うのが良いでしょうか」という質問に表示されている回答・コメント数は2件であること
を検証したい。どのようなテストコードを書けば良いか?
・・・というお題に対して、僕が考える解答例をいくつか提示してみる。
なお、この画面のHTMLは以下のようになっている。
繰り返し要素の各枠は .thread-list-item
回答・コメントの表示部分は .thread-list-item-comment
この記事の対象となるフレームワーク
- Ruby on Rails
- minitest または RSpec
なお、この記事のHTMLではtableタグは使っていないが、表形式で表示されている場合は次のように読み替えれば同じ考え方で対処できる。
-
.thread-list-item
= trタグ -
.thread-list-item-comment
= tdタグ内に表示されているテキスト
解法1:ループを回して目的の要素を探し当てる
ループで探し出すぶん、ちょっとパフォーマンスが悪くなる恐れがある。
element = all('.thread-list-item').find { |element| element.has_text?('どのエディターを使うのが良いでしょうか') }
within element do
# minitest
assert_selector '.thread-list-item', text: '回答コメント (2)'
# RSpec
expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
end
解法2:上から数えてn番目を決め打ちする
繰り返し要素が増減したり、時と場合によって並び順が変わったりしないことが前提。
within all('.thread-list-item')[4] do
# minitest
assert_selector '.thread-list-item', text: '回答コメント (2)'
# RSpec
expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
# Tips: 質問タイトルもあわせて検証しておけば、もし表示順が変わってしまったときも検知できる
assert_text 'どのエディターを使うのが良いでしょうか'
# もしくは
expect(page).to have_text 'どのエディターを使うのが良いでしょうか'
end
解法3:他のレコードを消して表示を1件だけにする
検索条件が指定できる画面であれば、対象のレコードだけが表示されるようにする。もしくは、他のレコードをDELETEしてから画面を表示する。テストする上で必ずしも他の要素も同時に表示する必要がない場合に有効。
# 他のデータを消してから画面を表示する
Question.where.not(title: 'どのエディターを使うのが良いでしょうか').destroy_all
visit questions_path
# 1件しか表示されないので within は不要
# minitest
assert_selector '.thread-list-item', text: '回答コメント (2)'
# RSpec
expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
解法4:viewを変更して、レコードのidを含むclass/idを付ける
viewを変更して良いなら、question-1234
のようにレコードのIDを識別できるclass属性またはid属性を付ける。
こうすれば「これ!」という1件をテストコード上でダイレクトに指定できる。個人的にはこの方法が一番オススメ。
within ".question-#{@question.id}" do
# minitest
assert_selector '.thread-list-item', text: '回答コメント (2)'
# RSpec
expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
end
応用:classやidではなくテスト専用の属性を使用する
ここまでのコード例ではすべてclass属性を参照してきたが、「テストで参照する要素はテスト専用の属性を用意して、それを参照すべきだ」という考え方もある。たとえば、以下の例では解法4でclass属性の代わりにdata-test
属性を使って対象のレコードを識別できるようにした例である。
# 注:本来であれば '.thread-list-item' も data-test 属性に置き換えた方が良い
within "[data-test=\"question-#{@question.id}\"]" do
# minitest
assert_selector '.thread-list-item', text: '回答コメント (2)'
# RSpec
expect(page).to have_selector '.thread-list-item', text: '回答コメント (2)'
end
テスト専用の属性を使うと、
- viewの都合でclass属性が変更されてもテストが壊れにくい
- デザイン上の都合ではなく、自動テストの都合で付けられた属性であることが第三者にとって明確になる
といったメリットがあるが、テストコードを書く手間が増えてしまうので、「明らかにテスト専用の属性を使った方がメリットが大きそうだ」と思うとき以外は、僕はあまり使わない。
2022.8.6追記
question-1234
のような属性を作る場合は、Railsのdom_id
メソッドを使うと便利です。
このメソッドを使うと、モデル名とレコードのidをアンダースコアでつないだ文字列が返ります。
dom_id(@question) #=> "question_1234"
また、応用的なアプローチとしてCapybaraに独自セレクタを追加する、という方法もあります。
Capybara.add_selector(:data_selector) do
css { |type| "[data-test='#{type}']" }
end
find(:data_selecotr, "question_#{@question.id}").click
詳しくは以下の記事を参照してください。
まとめ
ここで紹介した方法のどれかがきっと役に立つはず!!
あわせて読みたい
minitestであれ、RSpecであれ、システムテストやシステムスペックを書くときはCapybaraの知識が必要になる。Capybaraの使い方を知りたいときは以下の記事が便利!