RSpec 3.5 がリリースされました!

Sam Phippen, Myron Marston, Jon Rowe and Yuji Nakayama

Jul 1, 2016

RSpec 3.5 がリリースされました! 私たちは semantic versioning に準拠する方針を掲げているため、 このリリースはすでに RSpec 3 を使っている方にとってなにか対応が必要になるものではありません。 しかし、もし私たちがバグを作り込んでしまっていた場合は教えてください。 できるだけ早く修正をし、パッチ版をリリースします。

RSpec は世界中のコントリビュータと共に、コミュニティ主導のプロジェクトであり続けます。 今回のリリースには、50 人以上のコントリビュータによる 600 以上のコミットと 150 以上の pull request が含まれています!

このリリースに向けて力になってくれたみなさん、ありがとう!

主要な変更

Core: config.when_first_matching_example_defined

私たちは基本的に、 一部の spec でしか使わないセットアップロジックは spec_helper.rb には書かないようにすることをおすすめしています。 そうすることで、単独の spec を実行する際の起動時間を短縮できるからです。 そういったセットアップロジックは spec/support ディレクトリに入れると良いでしょう。 そしてそれを必要とする spec ファイルで、まずサポートファイルを require し、example group にタグをつけることで、 関連付けられたフックやモジュールを include することができます。

require 'support/db'

RSpec.describe SomeClassThatUsesTheDB, :db do
  # ...
end

この方法は上手くいきます。しかし、require と :db タグの両方が必要になるのは最適な方法ではないかもしれません。 その DB を利用するすべての spec において、毎回重複が発生してしまうからです。 また、もしその DB を使う spec に require 'support/db' を書き忘れてしまった場合、 その spec ファイル単体で実行すると失敗するけれども、 すべての spec を実行した場合にはパスする、 というような状況が起こりえます(他の spec ファイルがサポートファイルを読み込むので)。

RSpec 3.5 では、こういった状況で上手く使える新しいフックを導入しました。 support/db を必要とするすべての spec ファイルでそれを require する代わりに、 :db タグがついた example が定義されたタイミングでそれを自動的に読み込むように設定できます。

RSpec.configure do |config|
  config.when_first_matching_example_defined(:db) do
    require 'support/db'
  end
end

この新しい when_first_matching_example_defined フックは、 一致するメタデータを持つ最初の example が定義されたタイミングで発火し、 メタデータを基に必要なものを読み込むように設定できます。 もちろん、この仕組みは他にもいろいろな使い道があると思いますが、 これが私たちの想定している主なユースケースの一つです。

Core: config.filter_run_when_matching

RSpec のメタデータの一般的な使い方の一つとして、focus フィルタがあります。 RSpec 3.5 以前では、このように focus フィルタを設定していたでしょう。

RSpec.configure do |config|
  config.filter_run :focus
  config.run_all_when_everything_filtered = true
end

そして、example か example group に :focus タグをつけることで、RSpec にそれらのみを実行させることができます。 run_all_when_everything_filtered = true オプションは、 :focus タグがついたものが何もない場合に RSpec にこのフィルタを無視させるために使います。

しかしながら、run_all_when_everything_filtered は(:focus フィルタだけでなく) すべての フィルタに適用されるため、場合によっては予想外の挙動が発生してしまうことがあります (例えばこの issue)。 この問題に関して、私たちは :focus条件付きの フィルタとしてセットアップできれば上手くいくと考え、 RSpec 3.5 では以下のように設定できるようにしました。

RSpec.configure do |config|
  config.filter_run_when_matching :focus
end

この設定を使うと、:focus タグのついた example か example group が一つ以上存在する場合にのみ、 :focus フィルタが有効になります。また、以前より短く、シンプルな設定になります!

Core: コマンドラインで指定された順番で spec ファイルを読み込むように

RSpec 3.5 では、コマンドライン引数で渡された順番で spec ファイルやディレクトリを読み込むようになりました。 これによって、その場で簡単に順序を指定することができるようになります。 例えばその場で一度だけ、高速なユニット spec を、時間のかかる受け入れ spec の前に実行したいという場合、 このように RSpec を実行します。

$ rspec spec/unit spec/acceptance --order defined

この --order defined オプションは、 あなたのプロジェクトでデフォルトの実行順序をランダムに設定(推奨)している場合にのみ必要になります。

Core: Shared example group の include 方式の変更

これまで RSpec は shared context という概念 (任意のコンテキスト用のヘルパーメソッドやフックを共通化するための shared example group) をサポートしてきました。 Shared context はこんな風に定義します。

RSpec.shared_context "DB support" do
  let(:db) { MyORM.database }

  # それぞれの example をトランザクションで囲う
  around do |ex|
    db.transaction(:rollback => :always, &ex)
  end

  # SQL 文がどの example から発行されたのかわかるように
  # 開始/終了メッセージを DB のログに出力する
  before do |ex|
    db.logger.info "Beginning example: #{ex.metadata[:full_description}"
  end
  after do |ex|
    db.logger.info "Ending example: #{ex.metadata[:full_description}"
  end
end

この shared context を使うには、任意の example group で include_context を使って include します。

RSpec.describe MyModel do
  include_context "DB support"
end

また、 メタデータによって 暗黙的に shared context を include する方法もあります。

RSpec.shared_context "DB support", :db do
  # ...
end

# ...

RSpec.describe MyModel, :db do
  # ...
end

この方法はまあまあ上手くいきます。しかし、いくつかの問題がありました。

RSpec 3.5 では、いくつかの変更によってこれらの問題に対処しました。

新しい API: config.include_context

RSpec.configure ブロック内で、shared context の include 定義ができるようになりました。

RSpec.configure do |config|
  config.include_context "DB support", :db
end

これは、モジュールを include するための既存の config.include API と一貫しており、 メタデータを基に shared context を include するためのわかりやすい方法を提供します。 さらに、shared context をすべての example group に include することもできます(メタデータ引数を省略すれば良いだけ)。

新しい設定: config.shared_context_metadata_behavior

Shared context のメタデータの挙動を変更するための設定も追加されました。

RSpec.configure do |config|
  config.shared_context_metadata_behavior = :trigger_inclusion
  # または
  config.shared_context_metadata_behavior = :apply_to_host_groups
end

前者(:trigger_inclusion)はデフォルトの挙動であり、後方互換性のためだけに存在しています。 この場合、RSpec.shared_context に渡されたメタデータは RSpec 3.4 までと同様 (shared example group が、一致するメタデータを持つ example group に include されるように)に扱われます。 この挙動は RSpec 4 で廃止する予定です。

後者(:apply_to_host_groups)は、新しい挙動を有効にするためのものです。 この場合、RSpec.shared_context に渡されたメタデータは、 include した側の example group に適用されるようになります。 例えば DB shared context に :focus タグをつけることによって、 それに依存するすべての example group のみを実行することができます。

RSpec.shared_context "DB support", :focus do
  # ...
end

Expectations: respond_to マッチャのキーワード引数サポート

キーワード引数が Ruby の安定した言語機能となってしばらく経ちましたが、 これまで RSpec の多くのマッチャにおいて、キーワード引数に対するエクスペクテーションを設定することができませんでした。

rspec-expectations 3.5 では、 オブジェクトが任意のメソッドに応答するかどうかを、キーワード引数を使って検証できるようになりました。 メソッドが任意のキーワード引数を受け取るかどうかや、通常引数のカウントを検証することができます。

expect(my_object).to respond_to(:find).with_keywords(:limit, :offset)
expect(my_object).to respond_to(:find).with(1).argument.and_keywords(:limit, :offset)

さらに、幅のある引数カウントや、無限に引数を受け取れるかどうかも検証できます。

expect(my_object).to respond_to(:build).with(2..3).arguments
expect(my_object).to respond_to(:build).with_unlimited_arguments

これを実装してくれた Rob Smith、ありがとう。

Expectations: Minitest 5.6+ のサポート

rspec-expectations は通常、rspec-core と一緒に使われますが、 他のテストフレームワークで使うことも簡単にできます。 Minitest とのインテグレーションも提供しており、 Minitest 自身を読み込んだ後に rspec-expectations の Minitest サポートを読み込むことで利用できます。

require 'rspec/expectations/minitest_integration'

しかし Minitest は、バージョン 5.6 においてそれ自身の expect メソッドを導入したため、 RSpec の expect と衝突し、このインテグレーションが壊れてしまいました。 rspec-expectations 3.5 にはこのための修正が含まれています。

Mocks: Minitest インテグレーションの追加

私たちは以前から rspec-expectations の Minitest インテグレーションを提供していましたが、 rspec-mocks に関しては同レベルの簡単なインテグレーションを提供してきませんでした。 そのため、これまでユーザーは rspec-mocks のライフサイクルフックを使って Minitest と統合する必要がありました。 これは Minitest 5.6 で前述の expect メソッドが追加され、それによって動作が壊れるまでは問題なく動いていました。 rspec-mock 3.5 では、ファーストクラスの Minitest サポートを提供します。 これを利用するには、ただインテグレーションファイルを require するだけです。

require 'rspec/mocks/minitest_integration'

Rails: Rails 5 のサポート

今回の重大ニュースは、RSpec 3.5.0 が Rails 5 をサポートしたという点です。 私たちは、Rails 5 のベータや RC がリリースされると共に、並行して RSpec 3.5.0 のベータをリリースしてきました。 これは Rails のメジャーリリースなため、RSpec が使っているいくつかの API が deprecared 扱いになりましたが、 RSpec 3.5 はメジャーリリースではなく、私たちのユーザーに影響するのはコントローラーテストのみです。

Rails 5 では、 assignsassert_template が soft deprecated になりました。 コントローラーテスト自身は deprecated にはなっておらず:type => controller メタデータを spec に指定するのは未だ完全にサポートされています。 Rails 3 と 4 において、controller spec の assigns は慣用されてきました。 今回の RSpec 3.5 はマイナーリリースであり、私たちは SemVer に準拠する以上、 既存の controller spec を壊さないようにしています。 既存の Rails アプリケーションで assigns を多用しているものについては、 rails-controller-testing gem を Gemfile に追加することで assignsassert_template を復活させることができます。 RSpec はこの gem とシームレスに連携するため、controller spec は問題なく動作し続けるはずです。

これから新しく作成する Rails アプリケーションについては、 rails-controller-testing gem を追加するのはおすすめしません。 Rails チームや RSpec コアチームとしては、代わりに request spec を書くことを推奨します。 Request spec は一つのコントローラーアクションにフォーカスしますが、 controller spec とは違い、ルーターやミドルウェアスタック、Rack リクエストやレスポンスも関与します。 これによって、より現実に近い環境でテストを実行し、controller spec で発生しがちな多くの問題を避けることができます。 Rails 5 では、request spec が Rails 4までの request spec や controller spec よりもかなり高速になっています。 これは Rails チームの Eileen Uchitelle[^foot_1] のおかげです。

他の Rails 5 の重要な機能としては ActionCable があります。 残念ながら、RSpec は今のところ ActionCable をちゃんとテストするための方法を提供できていません。 Rails 側では、Rails 5.1 でリリース予定の ActionCable のための新しいテストタイプに取り組んでいます。 私たちは常にそれを注視し、準備ができた時には RSpec 側でも取り掛かる予定です。 それまでの間は、統合テスト的にブラウザー経由で ActionCable をテストすることをおすすめします。

Rails 5 の成果には RSpec コアチームメンバーによる貢献が含まれており、 また私たちも Rails コアチームに大きく助けられました。 このリリースに向けて力になってくれたみなさんに感謝します。

Stats

Combined:

rspec-core:

rspec-expectations:

rspec-mocks:

rspec-rails:

rspec-support:

Docs

API Docs

Cucumber Features

Release Notes

RSpec Core (combining all betas of RSpec 3.5.0)

3.5.0 / 2016-07-01

Full Changelog

Enhancements:

Bug Fixes:

3.5.0.beta4 / 2016-06-05

Full Changelog

Enhancements:

Bug Fixes:

3.5.0.beta3 / 2016-04-02

Full Changelog

Enhancements:

Bug Fixes:

3.5.0.beta2 / 2016-03-10

Full Changelog

Enhancements:

3.5.0.beta1 / 2016-02-06

Full Changelog

Enhancements:

Bug Fixes:

RSpec Expectations (including all betas of RSpec 3.5.0)

3.5.0 / 2016-07-01

Full Changelog

No user facing changes since beta4

3.5.0.beta4 / 2016-06-05

Full Changelog

Bug Fixes:

3.5.0.beta3 / 2016-04-02

Full Changelog

Enhancements:

3.5.0.beta2 / 2016-03-10

Full Changelog

Enhancements:

Bug Fixes:

3.5.0.beta1 / 2016-02-06

Full Changelog

Enhancements:

Bug Fixes:

RSpec Mocks (including all betas of RSpec 3.5.0)

3.5.0 / 2016-07-01

Full Changelog

Enhancements:

3.5.0.beta4 / 2016-06-05

Full Changelog

Enhancements:

3.5.0.beta3 / 2016-04-02

Full Changelog

Enhancements:

3.5.0.beta2 / 2016-03-10

Full Changelog

Enhancements:

Bug Fixes:

3.5.0.beta1 / 2016-02-06

Full Changelog

Bug Fixes:

RSpec Support (including all betas of RSpec 3.5.0)

3.5.0 / 2016-07-01

Full Changelog

No user facing changes since beat4

3.5.0.beta4 / 2016-06-05

Full Changelog

Enhancements: * Improve MethodSignature to better support keyword arguments. (#250, Rob Smith).

3.5.0.beta3 / 2016-04-02

Full Changelog

Bug Fixes:

3.5.0.beta2 / 2016-03-10

Full Changelog

No user-facing changes.

3.5.0.beta1 / 2016-02-06

Full Changelog

Footnotes

[^foot_1]: See also Eileen’s talk about request spec performance