落ちるRSpecをデバッグする
Rubyエンジニアとして開発をする中、ローカル環境では通っていたRSpecがCIでは通らないこと、何回か再走し直すことでようやく通るテストケースに遭遇すること、そもそもテストとして成立しているのか怪しいケースなど様々な壁にぶつかることがあるかと思います。
今回はメモとしてRSpecでテストが正常に作動しない場合にとれるデバッグ方法を載せていきます。
テスト内の変数を確認したい
プリントデバッグ
テスト内でputs
を記述してあげるだけです。RSpecに限った話ではないので説明不要かと思います。
describe '#full_name'
let(:user) { ... }
it 'ユーザーのフルネームが表示されること' do
puts user.last_name
expect ...
end
end
binding.pry
puts
の時と同様にテスト内で記述します。こちらも基本的な使い方と変わりません。
describe '#full_name'
let(:user) { ... }
it 'ユーザーのフルネームが表示されること' do
binding.pry # ブレークポイント
expect ...
end
end
テストが落ちた段階で実行を止めたい
あるテストが失敗してから後続のテストを全て待つのは面倒な時があると思います。
--fail-fast
をつけることで最初にテストが失敗した段階で自動的にRSpecの実行を停止してくれます。
bundle exec rspec --fail-fast
後ろに数字を指定すると、その回数分の失敗まで実行を止めません。
bundle exec rspec --fail-fast=3 # 3回失敗するまでテストを続行する
失敗したテストだけ実行したい
正常に動かない数件のテストのために何千のテストを逐一実行するのは時間の無駄です。
--only-failures
optionを利用することで直前に失敗したテストだけを実行することが可能です。
テスト実行前に、テストの実行結果を管理するファイルを指定します.
RSpec.configure do |config|
config.example_status_persistence_file_path = "spec/result.txt"
end
まずはプロジェクト全体でRSpecを流します。
bundle exec rspec
今回は失敗したテストが数件あったとします。
すると先ほど指定したspec/result.txt
には以下のように記録が残ります。
./spec/models/post_spec.rb[1:1:1:1] | failed | 0.10028 seconds |
./spec/models/post_spec.rb[1:1:2:1] | passed | 0.09612 seconds |
./spec/models/post_spec.rb[1:2:1:1] | passed | 0.10231 seconds |
result.txt
の中身には通ったテストと通っていないテストが記述されていることが分かります。
次に失敗したテストだけをデバッグしたい時に--only-failures
を付与すると、result.txt
でfailedにマークされたテストのみが実行されます。
bundle exec rspec --only-failures
失敗していたテストが通った場合はfailedからpassedに書き変わるので、次に--only-failures
を指定した時は実行されなくなります。
指定したテストだけ実行したい
RSpecには特定のテストのみを実行させるための機能がいくつか備わっています。
ファイル名/行数を指定する
rspecコマンドの後ろに相対パスを書くとそのファイルのみ実行してくれます。
bundle exec rspec spec/models/user_spec.rb:56 # user_spec.rbの56行目のテストケースのみを実行する
特定のディレクトリ配下のテストを全て実行したい場合はディレクトリで指定できます。
bundle exec rspec spec/models # models配下のテスト全てを実行
Inclusion filters
spec_helper
に以下の設定を記述します。
RSpec.configure do |config|
config.filter_run :focus
end
そうすることでfocus: true
と記述したテストケースだけ実行されるようになります。
it 'hoge' do
expect ...
end
it 'huga', focus: true do # このテストのみ実行される
expect ...
end
focusの書き方はいくつかあります。
fit 'hoge' do # it/context/describeの頭にfをつける
expect ...
end
focus 'hoge' do
expect ...
end
it 'hoge', :focus do
expect ...
end
focusの消し忘れを防止するためにrubucopでRSpec::Focusを設定しておくと良いです。
tag option
focus filterに近い方法ですが、テストケースにtagを設定することで、実行時にtagを指定すればその設定したテストのみを走らせることができます。
it 'hoge', priority: 'high' do # タグ
expect ...
end
bundle exec rspec test_spec.rb --tag priority: 'high' # priority: highのテストのみ実行
ref: rspec --tag options
example option
テストケースの名称を指定する方法です。
RSpec.describe User do
describe '#full_name' do
it 'ユーザーのフルネームが表示されること' do
expect ...
end
end
describe '#child?' do
it 'ユーザの生年月日から成人済みか判定すること' do
expect ...
end
end
end
bundle exec rspec spec/models/user_spec.rb --example 'User#full_name' # Userのfull_nameメソッドのみ実行
bundle exec rspec spec/models/user_spec.rb --example 'ユーザの生年月日から成人済みか判定すること' # 'ユーザの生年月日から成人済みか判定すること'という文言のテストケースを実行
ref: rspec example option
実行順序を指定したい
テストは実行順序によって通ったり通らなかったりすることもあります。
つまり「Aのテストの後にBのテストを実行すると成功するが、Bのテストの後にAのテストを実行すると失敗する」といった状態です。これはテストとして良くないので、どのような実行順序にしても成功するようにデバッグをする必要があります。
RSpecはデフォルトだとテストの実行順序はランダムに設定されていますがoptionに--order
を付与することで実行順序を指定することができます。
これを利用して、実行順序でテストが落ちた際に実行後に表示されるseed値を指定して再実行することで、失敗を再現することが可能です。
Randomized with seed 11111
bundle exec rspec test_spec.rb --order rand:11111
bundle exec rspec test_spec.rb --seed 11111 # これでもOK
もしくはrspecの結果をjsonに出力して再現する方法もあります。
bundle exec rspec \
--seed $(jq -r '.seed' result.json) \
$(jq -r '[.examples[].id] | join(" ")' result.json)
ref: Rails アプリケーションの不安定なテストを撲滅したい
テスト失敗時にスクリーンショットを撮りたい
system specで失敗した時の画面を確認するためにスクショを撮る方法です。
一番手軽な方法としてcapybara-screenshot
というgemを使用する手段があります。
このgemは内部でテストの失敗を検知して自動的にスクリーンショットを撮ってくれます。
group :test do
gem 'capybara-screenshot'
end
rails_helper
内でrequireするだけで設定が完了します。
require 'capybara-screenshot/rspec'
保存するディレクトリを明示的に書くこともできます。
Capybara.save_path = "/tmp/file_path"
ランダムに失敗するテストを再現したい
Rspec3.3以降から--bisect
optionというものが追加されました。
これは実行順序によって失敗するテストを最小のケース数で再現してくれるオプションです。
bundle exec rspec --bisect
実行すると以下のように失敗時の再現ができるコマンドを示してくれます。
The minimal reproduction command is:
rspec ./spec/test_3_spec.rb[1:1] ./spec/test_1_spec.rb[1:1] --seed 11111
後ろに=verbose
をつけると失敗時の再現コマンドを特定するまでの様子を出力します。
bundle exec rspec --bisect=verbose
Bisect started using options: "--seed 11111" and bisect runner: :fork
Running suite to find failures... (0.124353 seconds)
- Failing examples (1):
- ./spec/test_1_spec.rb[1:1]
- Non-failing examples (5):
- ./spec/test_2_spec.rb[1:1]
- ./spec/test_3_spec.rb[1:1]
- ./spec/test_4_spec.rb[1:1]
...
ref: rspec --bisect
Discussion