実際にはほとんど3.3の話ですが、まぁ3.3それだけよさそうなんで、みなさん使ってみてください。
まずはRSpecのブログにも書いてあったものから。
(3.2での変更) Core: Performance Improvements
オブジェクトを割り当てる回数を減らして速くしたそうです。RSpecが速すぎて困る人は誰もいません。上げましょう。
(3.2での変更) Mocks: Mismatched Args Are Now Diffed
expect(x).to eq(y)
して失敗した時と同じように、expect(x).to receive(:method_name).with(arg)
と書いて失敗した時も予測した結果と実際の結果のdiffが出るようになりました。
(3.3での変更) Core: New --only-failures
option
RSpec.configure do |c|
c.example_status_persistence_file_path = "./tmp/rspec-example-status.txt"
end
などとspec_helper.rb
に書いておくと、rspecを実行するたび./tmp/rspec-example-status.txt
に最後に実行したexampleの状態(ちゃんと中身見てませんが成功したとか失敗したとかの情報でしょう)を記録してくれるそうです。
そして--only-failures
オプションを使用すると、名前通りこのファイルを見て、「最後にrspec
を実行した時に失敗したexampleのみ実行」してくれるようになりました。素晴らしい!
(3.3での変更) Core: New --next-failure
option
先ほど紹介した./tmp/rspec-example-status.txt
に記録した情報を利用して、「最後にrspec
を実行した時に失敗したexampleのうち、最初に失敗するexampleまで実行する」というオプションです。
--only-failures --fail-fast --order defined
と等価なのだそうです。
(3.3での変更) Expectations: New aggregate_failures
API
※この節のサンプルコードは一部 http://rspec.info/blog/2015/06/rspec-3-3-has-been-released/ から引用しています。
例えば のように書くと
RSpec.describe Client do
let(:response) { Client.make_request }
it "returns a successful JSON response" do
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
example it "returns a successful JSON response"
の、各expectationを順番に実行して、1つでもexpectationが失敗した場合、exampleは失敗したとみなされ、実行を中断します。
ところが、下記のようにaggregate_failures
とタグを指定すると...。
RSpec.describe Client do
let(:response) { Client.make_request }
it "returns a successful JSON response", :aggregate_failures do
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
各expectationを順番に実行してexpectationが失敗した場合でも、実行を中断しないようになります(もちろんexample自体は失敗したことになります)。
これによって、こういうケースでit
ブロックをちまちま分ける必要がなくなり、結果before
ブロックを実行する回数が減り、実行速度を高めたり、より簡潔にexampleを書けるようになります。
これはbefore
ブロックでDBのレコードをたっぷり追加する、みたいなことをしている場合特に大きなメリットとなるでしょう。
しかし、名前がいかにもRSpecらしくない。言わなくても最初からそうしてほしいなぁ、
... と、思ったところ、下記のように設定すればデフォルトでそうできるんだとか。
RSpec.configure do |c|
c.define_derived_metadata do |meta|
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end
end
もしこの設定をした上で「aggregate_failures
したくない!」と思った時は先ほどのようにtagに:aggregate_failures
と書く代わりに
# ここに書けばこのブロック内すべてのexampleが`aggregate_failures`されます。
RSpec.describe Client, aggregate_failures: false do
let(:response) { Client.make_request }
# もちろん個別に指定してもOK!
it "returns a successful JSON response", aggregate_failures: false do
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
と書きましょう。
(3.3での変更) Expectations: Improved Failure Output
色々良くなったようです。特に**Time
オブジェクトが詳細に表示**されるようになったのが大きいかな?
気づいた時は喜び、そして感謝しましょう。
ChangeLogも流し読みしてみたよ!
- rspec-core/Changelog.md at v3.3.1 · rspec/rspec-core
- rspec-expectations/Changelog.md at v3.3.0 · rspec/rspec-expectations
- rspec-mocks/Changelog.md at v3.3.1 · rspec/rspec-mocks
- rspec-rails/Changelog.md at v3.3.2 · rspec/rspec-rails
- rspec-support/Changelog.md at v3.3.0 · rspec/rspec-support
で、個人的にめちゃめちゃ大きかったのが以下の2つ。
- Warn when using a bare
raise_error
matcher that you may be subject to false positives. (Jon Rowe, #768)
こういう風に、引数なしで
raise_error
を使用すると、警告が出るようになりました。
expect { subject_method }.to raise_error
このようにraise_error
の引数に何も指定しない場合、ブロックの中でどんなエラーが起きようとexampleが成功してしまいます。
例えばブロックの中で実行しているsubject_method
の名前が実は間違っていて本当はsubject_function
だったとしましょう。
それでもNoMethodError
が発生するので、このexampleは成功してしまうのです。
どんなエラーでも成功する、ということにすると、意図通り実装ができていないのに成功してしまうexampleを書いてしまう危険性が非常に高いのです。
こうした弊害を防ぐため、警告を出すようになりました。
これ、関係するIssueを個人的にwatchしていたほど気になっていた問題であったため、修正されたのは非常にありがたいです。
ただ、個人的な事情を申しますと、CircleCIにしてから警告をあまり見なくなってしまったため、その習慣を改めないといけませんね...。
- Ensure expectations that raise eagerly also raise during RSpec verification. This means that if exceptions are caught inside test execution the test will still fail. (Sam Phippen, #884)
例えばこういう実装で、
def just_hello
puts "hello"
frined.accept_hello_of(self)
rescue Exception
warn "I JUST WANT TO SAY HELLO!! DON'T DISTURB!!"
end
こういうexampleだった場合、
it 'says hello to no other person' do
expect(friend).not_to receive(:accept_hello_of)
x.just_hello
end
RSpec 3.3より前までは、expectationに反してfriend
がaccept_hello_of
しているにも関わらず、テストが通ってしまっていました。
なぜでしょう
just_hello
の実装のfriend.accept_hello_of(self)
が呼ばれた時点でrspec-mocksの例外が発生するも、just_hello
の中でrescue Exception
しているため、その例外までrescue
され、無視されてしまうからです。
RSpec 3.3からは、こうしたケースに対応するため、rspec-mocksが例外を発生させたとしても、exampleの実行終了後、mockのverifyをしたタイミングで必ず失敗するよう修正されました。
これによって、嬉しい弊害が発生しました。
どういうわけか我々のコードではこのrescue Exception
が多用されており、お陰でRSpec 3.3に上げた途端落ちるようになったexampleが17件もあったのです。
みなさんも移行の際はその辺を念頭に置きつつ、rescue Exception
にはくれぐれも気をつけましょう。
安易に使ってはいけません。