有頂天Ruby

ビールを飲みながらRubyについて書きます。

RSpec 3の重要な変更

Myron Marston » Notable Changes in RSpec 3の雑な訳です。

誤訳・雑すぎる訳がありましたら、Twitterで@nilp_までご連絡頂けると助かります。

RSpec 3.0.0 RC1が2日前にリリースされました、そして最終的な3.0.0のリリースが目前に迫っています。 我々はβ版をここ6ヶ月にわたり使ってきました、我々はそれらを皆さんと共有できることにわくわくしています。

これが新しいとこだよ:

すべてのgemたちにわたって

Ruby 1.8.6と1.9.1のサポートがなくなりました

これらのバージョンのRubyはかなり前に寿命を迎えました、RSpecはこれらをサポートしません。

Ruby 2.xのサポート向上

最近のRSpec 2.xのリリース(すなわち2.0がリリースされたあと出たやつ)はRuby 2を公式にサポートしています、しかしRSpec 3でのサポートはより向上しました。 現在我々はRuby 2の新機能のためのサポートを提供しています、たとえばキーワード引数やprependされたモジュールなどです。

æ–°gem rspec-support

rspec-supportは、我々が1つ以上のrspec-(core|expectations|mocks|rails)で必要としている共通したコードのために使用している新しいgemです。 それは今のところエンドユーザーあるいは拡張ライブラリの作者が使えるようなpublicなAPIは含まれていません、しかし我々は将来いくつかのAPIをpublicにするかもしれません。 もしあなたがGemfileでGitHubをソースに指定して、最新版RSpecを実行しているなら、あなたはrspec-supportのために同じ指定をする必要があります。

堅牢で、よくテストされたアップグレードプロセス

RSpec 3における全ての破壊的な変更は2.99でのdeprecation warningに相当します。 βの間、我々はアップグレードが出来る限りスムーズに進むよう多くのアップグレードを行いました。 我々は段階的にアップグレードするための説明を1つにまとめてあります。

アップグレードのプロセスはRSpecのすごく設定の柔軟性が高い、新しいdeprecationシステムにも目を向かせます(deprecationをファイルに出力したり、全てのdeprecationをエラーにできる)、 そして、それは重複したdeprecationの出力を少なくするように設計されています。

改善されたドキュメント

我々は全てのgemのAPIドキュメントをアップデートするためクソ頑張りました。 それらは現在rubydoc.infoでホストされています:

...しかし、現在われわれはこれらをセルフホストするために、rspec.infoの更新を行っています。

ドキュメントは今も作業中です(実のとこいつも作業中)、

我々はSemVerの一環として全てのパブリックAPIを明確に公表できるようにしました。 我々は全ての3.xのリリースの間、全てのパブリックAPIをメンテナンスするために全身全霊を尽くしています。 一方で、プライベートAPIについては、3.xのリリース中のどこかでそれらを変更できるような柔軟性を確保したかったのでプライベートとなっています。

どうか、我々がプライベートと宣言したAPIを使わないでください。 もしあなたが既存のpublic APIによって取り上げられていないニーズを見つけたら、どうぞ尋ねてください。 我々はなたのニーズのため喜んでプライベートAPIをpublicにするか、新しくあなたのユースケースに合ったAPIをつくるでしょう。

Gemに署名がついたなう

我々は私達のgemに署名をつけはじめました。 しばらくはgemの署名システムは理想とは程遠いですが、 よりよいソリューションが開発されはじめています、 しかしないよりはマシです。 我々は私達の公開鍵をGitHubに置いています。

現在のgem署名システムについてより詳しいことが知りたければ、A Practical Guide to Using Signed Ruby Gemsを見てください。

ゼロモンキーパッチモード

RSpecは今いかなるモンキーパッチもなしで使うことができます。 このための大部分の土台が新しくexpectベースの記法をrspec-expectationsとrspec-mocksに追加した最近の2.xのリリースで作られました。 我々はRSpec 3への道のりで、残りの部分をとりのぞき、残っているモンキーパッチについても代替を提供しました。

便利なことにあなたは全てのモンキーパッチを1つのオプションで無効にできます:

spec/spec_helper.rb

RSpec.configure do |c|
  c.disable_monkey_patching!
end

この設定オプションを実装してくれてありがとうAlexey Fedorov。

より詳しい情報:

rspec-core

フックスコープのための新しい名前: :example と :context

RSpec 2.x は3つの異なるフックスコープをもっていました:

my_class_spec.rb

describe MyClass do
  before(:each) { } # このグループのそれぞれのexampleの前に実行される
  before(:all)  { } # このグループの最初のexampleの前に1度だけ実行される
end

spec/spec_helper.rb

RSpec.configure do |c|
  c.before(:each)  { } # 全てのテストスイート中のそれぞれのexampleの前に実行される
  c.before(:all)   { } # それぞれのトップレベルのグループの最初のexampleの前に実行される
  c.before(:suite) { } # 全てのspecファイルがロードされたあと、最初のspecが実行される前に一度だけ実行される
end

ときどき、ユーザーは:each vs :allが何を意味するか混乱することと、特に:allをconfigブロック中で使ったときに混乱することを述べていました:

spec/spec_helper.rb

RSpec.configure do |c|
  c.before(:all) { }
end

このcontextでは:allという言葉はテストスイート中の全てのexampleの前に一度のみ実行されることを推測してしまいます、しかしそは:suiteのことです。

RSpec 3では、:eachと:allはスコープをより明確にするエイリアスを持っています: :exampleは:eachのエイリアスで:contextは:allのエイリアスです。 :eachと:allはdeperecatedではなく我々がそうする計画もないことに注意してください。

これを実装してくれてありがとうJohn Feminella。

より詳しい情報:

DSLメソッドがexampleを引数として渡す

RSpec::Core::Exampleはexampleに関する全ての詳細にアクセスする手段を提供します: その説明、場所、メタデータ、実行結果、などなど。 RSpec 2.xではexampleはexampleメソッドとしてどんなフックや個別のexampleからでもアクセス出来るようにさらされていました:

my_class_spec.rb

describe MyClass do
  before(:each) { puts example.metadata }
end

RSpec 3では、我々はexampleメソッドを取り除きました。 その変わりに、instanceは全てのexampleスコープのDSLメソッドにはっきりと引数として渡されるようになりました。

my_class_spec.rb

describe MyClass do
  before(:example) { |ex| puts ex.metadata }
  let(:example_description) { |ex| ex.description }

  it 'exampleにアクセス' do |ex|
    # exを使う
  end
end

それをアイデアと共に実装してくれてありがとうDavid Chelimsky!

より詳しい情報:

rspec-coreのモンキーパッチングを無効にするための新設定オプションexpose_dsl_globally

RSpec 2.xはトップレベルメソッドのdescribeや、shared_examples_forやshared_contextを提供するためmainとModuleをモンキーパッチしていました。

my_gem_spec.rb

shared_examples_for "something" do
end

module MyGem
  describe SomeClass do
    it_behaves_like "something"
  end
end

RSpec 3では、これらのメソッドはRSpecモジュール上で利用出来るようになりました(それに加えてモンキーパッチとしても利用可能です)。:

RSpec.shared_examples_for "something" do
end

module MyGem
  RSpec.describe SomeClass do
    it_behaves_like "something"
  end
end

新しいexpose_dsl_globallyの設定オプションをfalseに設定することによって、あなたはrspec-coreのモンキーパッチを完全に取り除くことができます(それにより上で出てきた最初のexampleはNoMethodErrorの例外を投げるようになります)。

spec/spec_helper.rb

RSpec.configure do |config|
  config.expose_dsl_globally = false
end

これを実装してくれてありがとうJon Rowe。

より詳しい情報:

alias_example_group_toでexample groupにエイリアスを定義できます

RSpec 2.xでは、我々はあなたがメタデータ付きのexampleのエイリアスを定義できるAPIを提供しました。 例えば、これは:forcus => trueのメタデータを伴ったitのエイリアスのfitを定義するために内部で使われていました:

spec/spec_helper.rb

RSpec.configure do |config|
  config.alias_example_to :fit, :focus => true
end

RSpec 3では、我々はexample groupsでもこの機能を利用出来るよう拡張しました:

spec/spec_helper.rb

RSpec.configure do |config|
  config.alias_example_group_to :describe_model, :type => :model
end

あなたはこの例をrspec-railsを使っているプロジェクトで利用し、describe User, :type => :modelのかわりにdescribe_model Userを使えます。

これを実装してくれてありがとうMichi Huber

より詳しい情報: - Documentation - rspec-core #493 - original discussion

example groupの新しいエイリアス: xdescribe, xcontext, fdescribe, fcontext

example groupのエイリアスを定義するためのAPIを加えるだけではなく、我々は同様にいくつかの追加のビルトインエイリアスを加えました(describeとcontext上に):

  • xdescribe/xcontext、たとえばxitのようなものです、一時的に1つのexampleグループをスキップするために使えます。
  • fdescribe/fcontext、たとえばfitのようなものです、一時的に1つのexampleグループに:focus => trueのメタデータを加えるために使えます、あなたはconfig.filter_run :focusによって関心を持ったexamplesとgroupsを簡単にフィルターできます。

より詳しい情報:

pendingの意味の変更(そしてskipの紹介)

Pending exampleは今、ほんとは通っているのかをチェックするために実行されます。 もしpendingブロックが失敗したら、それは前と同じようにpendingとして記録されます、 一方、もしそれが成功した場合、それは失敗したことになります。 これはexampleらを保留にしたことと、それらが説明していることが実装されたとき保留を迅速に解決することを確実にする手助けをします。

"決して実行しない"古い動作をサポートするために、skipメソッドとメタデータが加えられました。 次のexampleらはどれも実行されることはありません:

post_spec.rb

describe Post do
  skip 'not implemented yet' do
  end

  it 'does something', :skip => true do
  end

  it 'does something', :skip => 'reason explanation' do
  end

  it 'does something else' do
    skip
  end

  it 'does something else' do
    skip 'reason explanation'
  end
end

この変更によって、あるexample中でpendingにブロックをわたすことは納得いかない動作になりました、なのでその動作は取り除かれました。

これを実装してくれてありがとうXavier Shay。

より詳しい情報:

ワンライナーのための新しいAPI: is_expected

RSpecはワンライナーのための記法を長い間持っていました:

post_spec.rb

describe Post do
  it { should allow_mass_assignment_of(:title) }
end

このcontextでは、shouldはモンキーパッチされていませんがshouldは:expect記法のみをサポートするrspec-expectationsの設定によって取り除くことが出来ます。 この場合はshouldを使えるようにObjectをモンキーパッチするのに嫌な気はしません、これはあなたの記法の設定にかかわらず常に利用可能です。

何名かのユーザーはもしこれを使い続けることが出来るなら、このshouldとexpect記法をどう関連づけるのかについて戸惑いをみせました。 これはRSpec 3でも利用可能なままです(もう一度いいますがあなたの記法の設定に関係なく)、しかし我々は同様にexpect記法に矛盾しない代替のAPIを追加しました:

post_spec.rb

describe Post do
  it { is_expected.to allow_mass_assignment_of(:title) }
end

is_expectedはexpect(subject)として非常に簡単に定義されており、should_notと同様に否定する検証をis_expected.not_toマッチャーによりサポートします。

より詳しい情報:

Exampleグループの実行順を個別に設定できる

RSpec 2.8からrandomな実行順をRSpecに取り入れました、これはあなたのspecスイート中にある故意ではない実行順依存を明らかにするのに非常に役に立つでしょう。 RSpec 3では、単にオールオアナッシングの機能ではなくなりました。

あなたはexampleグループごとに合ったメタデータでタグ付けし、個別に実行順序をコントロールすることができます。

my_class_spec.rb

describe MyClass, :order => :defined do
  # 実行順の設定にかかわらず、
  # このグループ中のexampleらは常に定義された順序で実行されます。
end

describe MyClass, :order => :random do
  # 実行順の設定にかかわらず、
  # このグループ中のexampleらは常にランダムな順序で実行されます。
end

これは特に実行順を定義順からランダムな順序に切り替えるのにやくだちます、 あなたは問題を一度にすべて解決しようとせず、特定のグループに対してこの機能を用いていくことで、実行順序の依存を1つずつ解決していくことが可能です。

この一環として、我々は同様に--order defaultの名前を--order definedに変更しました、なぜなら我々は "default" が非常に多岐に渡る意味を持つと実感したからです。

この機能を実装するのを助けてくれてありがとうAndy LindemanとSam Phippen。

より詳しい情報: - Documentation

新しいオーダーリングストラテジーAPI

RSpec 3では、我々はオーダーリングストラテジーAPIをオーバーホールしました。 依然は 3つの、 異なる、 メソッド だったのが1つのメソッドになりました: register_orderingです。 オーダーリングストラテジーに名前をつけるためにこれを使ってください。

spec/spec_helper.rb

RSpec.configure do |config|
  config.register_ordering(:description_length) do |list|
    list.sort_by { |item| item.description.length }
  end
end

my_class_spec.rb

describe MyClass, :order => :description_length do
  # ...
end

あるいはあなたはこれをグローバルなオーダーリングとして使うことができます:

spec/spec_helper.rb

RSpec.configure do |config|
  config.register_ordering(:global) do |list|
    # アルファベット順にソートする
    list.sort_by { |item| item.description }
  end
end

:globalのオーダーリングはトップレベルのexample groupsと、:orderのメタデータをもたない全てのexample groupsを順番付けるために使われます。

より詳しい情報:

rspec --initのカイゼン

rspecコマンドは長い間、プロジェクトスケルトンをセットアップするために--initオプションを提供してきました。 RSpec 3では、そのコマンドが作成するファイルが、箱から出してそのまま使えるよりよいものを提供するため、かなりカイゼンされました、そして推奨する設定と一緒にspec/spec_helper.rbファイルを提供します。

あ、推奨する設定の中でデフォルトになる予定がないものは生成されたファイル中でコメントアウトされています、 なので、ファイルを開いて、あなたがいいな〜と思うオススメ設定を受け入れるといいですよ。

より詳しい情報:

新しいコマンドラインオプション --dry-run

このオプションはいかなるexampleもhookも実行せず、あなたのspecスイートをformatterを使って出力した結果を画面に表示します。 これは、テストが通る/落ちるを気にしたり、テストが実行されるのをまったりせずに、あなたのテストスイートのドキュメント化されたアウトプットをレビューするのに特に役にたちます。

これに貢献してくれてありがとうThomas Stratmann!

より詳しい情報:

フォーマッターのAPIが変わりました

より柔軟な完全に新しいフォーマッターAPIが追加されました。

  • あなたが欲しいイベントのみを受け取れます
  • メソッドは、具体的なパラメーターではなく通知オブジェクトを受け取ります、なので後方互換性のマナーにのっとり新しい通知データを追加することが出来るようになりました
  • ヘルパーメソッドは通知オブジェクト上で使えるようになったのでBaseTextFormatterを継承する必要がなくなりました

新しいフォーマッターはこんな感じになるでしょう:

custom_formatter.rb

class CustomFormatter
  RSpec::Core::Formatters.register self, :example_started

  def initialize(output)
    @output = output
  end

  def example_started(notification)
    @output << "example: " << notification.example.description
  end
end

古い2.xのフォーマッターAPIを引き続きサポートするためにrspec-legacy_formatters gemが提供されます。

この役を引き受けてありがとうJon Rowe。

より詳しい情報:

アサーションの設定が変わりました

ほとんどのユーザーはrspec-expectationsを使っていますが、実は他のものを使うこともできます、RSpec 2.xは一番一般的な代替を設定オプションで簡単に利用することができました:

spec/spec_helper.rb

RSpec.configure do |config|
  config.expect_with :stdlib
  # あるいは、両方を使う
  config.expect_with :stdlib, :rspec
end

さて、この:stdlib周りで混乱がありました。 Ruby 1.8では、標準のアサーションモジュールはTest::Unit::Assersionsです。 1.9+では、それはMinitesst::Assertions上の薄いラッパーです(あなたが単にそれだけを使うならうまくいきます)。 そうしているうちに、minitest gemとtest-unit gemも同様にTest::Unit::Assertionsを定義しました。 RSpec 3では、我々はexpect_with :stdlibを取り除きました、その代わりに明確に:test_unitか:minitestオプションを選択するようにしました:

spec/spec_helper.rb

RSpec.configure do |config|
  # for test-unit:
  config.expect_with :test_unit

  # for minitest:
  config.expect_with :minitest
end

これを実装してくれてありがとうAaron Kromer。

より詳しい情報:

〜由来のメタデータ定義

RSpecのメタデータシステムは超絶に柔軟で、あなたはテストスイートをいろんな切り口で扱うことができます。 この新しい設定APIは、あなたが〜由来のメタデータを定義することを可能にします。 例えば、spec/acceptance/js以下の全てのexample groupを:js => trueでタグづけしたりできます:

spec/spec_helper.rb_

RSpec.configure do |config|
  config.define_derived_metadata(:file_path => %r{/spec/acceptance/js/}) do |metadata|
    metadata[:js] = true
  end
end

より詳しい情報:

なくなったもの

いくつかのものについては、RSpecのコアに含まれなくなり、全て取り除かれるか外部gemとして切りだされました:

  • TextmateフォーマッターはTextmate bundleに移動しました。たった1つのテキストエディタのためのフォーマッターをrspec-coreの中に持つのは筋が通りません。
  • RCovとの統合はなくなりました。これが1.9+で動くようにアップデートされることはありませんでした、最近では我々は代わりにsimplecovを使うことをおすすめしてます。
  • コマンドラインオプションの--debugをとりのぞきました。最近では非常におおくのデバッガーの選択肢があります、そしてあなたはそれらをコマンドラインから--require(あるいは-r)オプションを使って有効にできます。例えば、byebugを使うには、-rbyebugをコマンドラインで渡します。
  • 我々はコマンドラインオプションの--line-numberをとりのぞきました。これは先頭にある場合意味がはっきりせず(--line-number 43はロードされた全てのspecファイルの43行目の近くで定義されたexampleを選択するが、それぞれのファイルの43行目が関係ある理由がない)、よりぶっきらぼうな感じのpath/to/spec.rb:43の形と重複しています。
  • itsが新しいrspec-its gemに切りだされました、これは親切にもPeter Alfvinがメンテナンスすることを申し出てくれました。
  • Autotestとの統合が新しいrspec-autotest gemに切りだされました(これにはメンテナーが必要です: だれかボランティアはいませんか?)。

rspec-expectations

should記法を明示的に有効にせずに使うのはdeprecatedになりました

RSpec 2.11で我々は新しいexpectベースの記法を導入することで、モンキーパッチをRSpecから外す方向に進みはじめました。 RSpec 3では、我々はshould記法をもち続けます、そしてそれはデフォルトで利用可能です、しかしあなたが明示的にそれを有効にしない場合、あなたはdeprecation warningを受けとるでしょう。 これは、古いチュートリアルで新しくRSpecを学びはじめた人の混乱を避け、RSpec 4でデフォルトでそれを無効にするため(あるいは別のgemとして切り離す可能性もあります)の状況を整えるでしょう、 我々はexpect記法をRSpecの "main" の記法にすることをきめました、しかしあなたが古いshouldベースの記法を好むなら、それを使い続けることもできます: 我々にはそれを無くしてしまう計画はありません。

これを実装してくれてありがとうSam Phippen。

より詳しい情報:

マッチャー合成式

RSpec 3では、あなたは複数のマッチャをandとorを使ってつなげて一緒にすることができます:

compound_examples.rb

# この2つのexpectationsが...
expect(alphabet).to start_with("a")
expect(alphabet).to end_with("z")

# ...ガッチャンコして1つの式になれる
expect(alphabet).to start_with("a").and end_with("z")

# もちろん`or`も同じように使えます:
expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")

それらは&と|演算子にエイリアスされています。

compound_operator_examples.rb

expect(alphabet).to start_with("a") & end_with("z")
expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")

この機能を提案し実装してくれてありがとうEloy Espinaco、そして&と|演算子でそれを拡張してくれてありがとうAdam Farhi。

より詳しい情報:

コンポーザブルマッチャー

RSpec 3は、マッチャーを引数として他のマッチャーに渡すことで、あなたの意思を詳細に表現することを可能にします。

composed_matcher_examples.rb

s = "food"
expect { s = "barn" }.to change { s }.
  from( a_string_matching(/foo/) ).
  to( a_string_matching(/bar/) )

expect { |probe|
  "food".tap(&probe)
}.to yield_with_args( a_string_starting_with("f") )

コードの表現と失敗時のメッセージの可読性を向上させるため、ほとんどのマッチャーは、それらの式が引数として渡されたとき、きちんと読めるようなエイリアスをもっています。

より詳しい情報:

matchマッチャーをデータ構造に対して使うことができます

RSpec 3以前では、matchマッチャーは#matchメソッドを使って文字列/正規表現のマッチングを行うために存在していました:

match_examples.rb

expect("food").to match("foo")
expect("food").to match(/foo/)

RSpec 3では、それは任意のネストした配列/ハッシュのデータ構造のマッチングを追加でサポートします。 ネストの中のどのレベルでも、期待する値をマッチャーを使って表現することができます:

match_data_structure_example.rb

hash = {
  :a => {
    :b => ["foo", 5],
    :c => { :d => 2.05 }
  }
}

expect(hash).to match(
  :a => {
    :b => a_collection_containing_exactly(
      an_instance_of(Fixnum),
      a_string_starting_with("f")
    ),
    :c => { :d => (a_value < 3) }
  }
)

より詳しい情報:

新しいallマッチャー

このマッチャーをつかえば、あなたはコレクションの中の全ての要素の何かがtrueであることを記述出来るようになります。引数としてマッチャーを渡します:

all_example.rb

expect([1, 3, 5]).to all( be_odd )

これに貢献してくれてありがとうAdam Farhi

より詳しい情報:

新しいoutputマッチャー

このマッチャーをつかうと、そのブロックがstdoutもしくはstderrに書く内容を記述できるようになります。

output_examples.rb

expect { print "foo" }.to output("foo").to_stdout
expect { print "foo" }.to output(/fo/).to_stdout
expect { warn  "bar" }.to output(/bar/).to_stderr

これを提案してくれてありがとうMatthias Günther(それにスタートをキメてくれた)、 そしてこの機能をフィニッシュラインに導いてくれてありがとうLuca Pette。

より詳しい情報:

新しいbe_betweenマッチャー

RSpec 2は動的な述語サポートを使い、between?を実装してるオブジェクトに対してbe_betweenマッチャーを提供してきました。

RSpec 3で、我々はbe_betweenマッチャーを一級市民に押し上げることにしました、これにはいくつかのよい面があります:

  • 失敗時のメッセージが単に「between?(1, 2)がfalseを返した」というより、よくなりました
  • それは、between?を実装していなくても、比較演算子を実装したオブジェクトで動作します(e.g. <, <=, >, >=)
  • それはinclusiveとexclusive2つのモードを提供します。

be_between_examples.rb

# `Comparable#between?`のように、デフォルトだとinclusiveです
expect(10).to be_between(5, 10)

# ...しかし、あなたはそれをexclusiveにすることもできます:
expect(10).not_to be_between(5, 10).exclusive

# ...あるいは明確にinclusiveのラベルを付けることもできます:
expect(10).to be_between(5, 10).inclusive

これに貢献してくれてありがとうErik Michaels-Ober、 そしてよくしてくれてありがとうPedro Gimenez。

より詳しい情報:

Booleanマッチャーの名前がかわりました

RSpec 2はペアのマッチャーを持っていました(be_trueとbe_false)これはRubyの条件の意味を反映しています: be_trueはnilかfalse以外の全ての値で通ります、そしてbe_falseはnilかfalseの場合通ります。 RSpec 3では、我々はこれらをより明確な意味になるようbe_truthyとbe_falsey(もしくはbe_falsy、もしあなたがこのスペリングのほうが好みなら)に変更しました、 そしてbe true/be falseとの混乱をなくしました(be_true/be_falseと同じように読めるが、ほんとにtrue/falseのときしか通らない)。

これを実装してくれてありがとうSam Phippen

より詳しい情報:

match_arrayマッチャーがcontain_exactlyとして利用できるようになった

RSpecは長い間、このマッチャーをあなたが2つの配列の内容を順番を気にせずに比較することに使えるようにしてきました。 もともとは、これは古いshould記法で=~を使うことで利用することができました:

match_array_operator_example.rb

[2, 1, 3].should =~ [1, 2, 3]

その後、我々がexpect記法を追加したとき、演算子のマッチャーを新しい記法に持ち込まないことを決めました、そしてこのマッチャーのことをmatch_arrayと呼ぶことにしました:

match_array_example.rb

expect([2, 1, 3]).to match_array([1, 2, 3])

match_arrayはそのとき私達が一番いいと考えた名前でした、 しかし我々はそれを手放で喜ぶわけにはいきませんでした: "match"は不明確な言葉遣いです、このマッチャーは配列以外の種類のコレクションでもうごくことを意図しています。 RSpec 3で我々はこのマッチャーによりよい名前をつけました:

contain_exactly_example.rb

expect([2, 1, 3]).to contain_exactly(1, 2, 3)

気をつけて欲しいのはmatch_arrayはdeperecatedではないことです。 match_arrayは1つの配列を引数で受け取る一方、contain_exactlyは展開された要素を個別に受け取る(訳注: *splatどう訳すか)こと以外は、この2つのメソッドはまったく同じです。

より詳しい情報:

コレクション数マッチャーがrspec-collection_matchers gemに切りだされました

コレクション数マッチャー―have(x).items、have_at_least(y).itemsそれにhave_at_most(z).items―はRSpecの魔法じみて混乱するパーツの1つでした。 それらは切りだされて、rspec-collection_matchers gemに移されました、

親切にもHugo Baraúnaがメンテナンスするのに名乗りをあげてくれました。

より一般的な代替はコレクションのsizeに検証を置くことです:

collection_matcher_examples.rb

expect(list).to have(3).items
# ...これはこのように書けます:
expect(list.size).to eq(3)

expect(list).to have_at_least(3).items
# ...これはこのように書けます:
expect(list.size).to be >= 3

expect(list).to have_at_most(3).items
# ...これはこのように書けます:
expect(list.size).to be <= 3

Minitestとの統合のカイゼン

RSpec 2.xでは、rspec-expectationsは自動的に自身をMiniTest::Unit::TestCaseかTest::Unit::TestCaseにincludeしました、なのであなたは単にMinitestやTest::Unitをロードするだけでrspec-expectationsを使うことができました。

RSpec 3では、我々はこの統合を2つの方法でアップデートしました:

  • Minitest 4(あるいはそれ以下)とTest::Unitとの統合が自動的に行われなくなりました。もしあなたがrspec-expectationsをそのような環境で使うなら、あなたはRSpec::Matchersをあなた自身でインクルードする必要があります。
  • Minitest 5とのよりよく統合が提供されました、しかしあなたはrequire 'rspec/expectations/minitest_integration'でそれを明示的にロードする必要があります

より詳しい情報:

マッチャープロトコルの変更

上で述べたように、RSpec 3では、我々はshouldをrspec-expectationsのメインの記法にすることをやめました。 我々はこれを反映してマッチャープロトコルをアップデートしました:

  • failure_message_for_shouldはいまfailure_message
  • failure_message_for_should_notはいまfailure_message_when_negated
  • match_for_should (カスタムマッチャDSLのmatchエイリアス)には変わりのものは用意されず、とりのぞかれました(単にmatchを使ってください)
  • カスタムマッチャDSLのmatch_for_should_notはいまmatch_when_negated

それに加え、我々はsupports_block_expectations?を新しく追加しました、マッチャープロトコルのオプション部分です。 これはもしユーザーがvalueマッチャをブロックexpectation式中で間違ってつかったときに分かりやすいエラーを与えます。 例えば、この変更の前では、be_nilのようなマッチャを使うときに、blockをexpectに渡すことで、誤った結果を導いてしまいました:

block_expectation_gotcha.rb

expect { foo.bar }.not_to be_nil

# ...これは次と同様の意味です:
block = lambda { foo.bar }
expect(block).not_to be_nil

# ...しかし、ブロックはnilではありません(たとえ`foo.bar`がnilを返すとしても)、
# なのでユーザーの意図が次のようなものだったとしても、expectationが通ってしまいます:
expect(foo.bar).not_to be_nil

気をつけてほしいのは、supports_block_expectations?はマッチャープロトコルのオプションの部分だということです。 ブロックexpectation式で使ってほしくないマッチャのために、あえてこれを定義する必要はありません。

より詳しい情報:

rspec-mocks

モンキーパッチしたシンタックスを明示的に有効にせずに使うのはdeprecated

rspec-expectationsと共に、我々は rspec-mocks をゼロモンキーパッチのシンタックスへ向けて進みはじめました。 これらは2.14から導入されました、 RSpec 3では、あなたがオリジナルの記法(e.g. obj.stub、obj.should_receive、etc)を明示的に有効にせずに使うとdeprecation warningを出すようになりました(rspec-expectationの新しい記法と同様です)。

これを実装してくれてありがとうSam Phippen

receive_messagesとreceive_message_chainのための新しい記法

もともとのモンキーパッチの記法には2.14でリリースされた新しい記法にはかけていた機能がいくつかありました。 我々はRSpec 3でこれに対して2つの新しいAPIを提示します: receive_messagesとreceive_message_chainです。

examples.rb

# 古い記法:
object.stub(:foo => 1, :bar => 2)
# 新しい記法:
allow(object).to receive_messages(:foo => 1, :bar => 2)

# 古い記法:
object.stub_chain(:foo, :bar, :bazz).and_return(3)
# 新しい記法:
allow(object).to receive_message_chain(:foo, :bar, :bazz).and_return(3)

これらの新しいAPIの利点の1つがこれらはexpectと組み合わせても、同じように動くことです、一方で古い記法のstub(hash)やstub_chainではこれと同じことはできません。

これを実装してくれてありがとうJon RoweとSam Phippen。

doubleのエイリアスのmockとstubを取り除きました

歴史的経緯により、rspec-mocksはテストダブルを作るために3つのメソッドを提供してきました: mock、stubとdouble。 RSpec 3では、我々はstubとmockをとりのぞき単なるdoubleを使うことを選択しました、そしてdoubleの上にさらなる機能を体系だてていくことにしました(veryfying doubleみたいな、下の方をみてください)。 もちろん、RSpec 3はdoubleにたいしてmockとstubのエイリアスを提供しませんが、あなたがこれらを使い続けたいならば、これらのエイリアスを定義するのは簡単です:

spec/spec_helper.rb

module DoubleAliases
  def mock(*args, &block)
    double(*args, &block)
  end
  alias stub mock
end

RSpec.configure do |config|
  config.include DoubleAliases
end

これを実装してくれてありがとうSam Phippen。

より詳しい情報:

doubleらを検証する

あなたが実際に存在するメソッドのみをstubあるいはモックしていることを保証するため、新しい種類のdoubleが追加されました、それは渡された引数を宣言されたメソッドシグネチャによって確認します。 instance_doubleとclass_double、そしてobject_doubleのdoubleらは、これらの条件が満たされない時すべてにおいて例外を投げます。

もしクラスがロードされていないとき(通常はユニットテストを独立して実行している際)、例外が発生することはありません。 これは複雑な動作です、しかし独立したユニットテストの速度を高め、インテグレーションテスト(あるいは型システム)に近づくにつれ信頼でき、非常に強力です。 これは大変奇妙な動作に見えるかもしれませんが、独立したユニットテストに速度と、インテグレーションテスト(あるいは型システム)に近い信頼性を与えます。(訳注:ここらへん訳が不安) これらの新しくよりパワフルなdouble型を使うべきではない理由はあまりないでしょう。

この機能のアイデアと実装をありがとうXavier Shay

より詳しい情報:

部分的なdoubleの検証の設定オプション

doubleらを検証する動作は部分的なdoubleにおいてはグローバルに有効になっています。 (部分的なdoubleとはあなたが既存のオブジェクトをmockあるいはstubしたときです: expect(MyClass).to receive(:some_message))

spec/spec_helper.rb

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

我々はあなたがこの設定を全ての新しいコードで有効にすることを推奨しています。

スコープが変わりました

rspec-mocksのオペレーションは、テストごとのライフサイクルを念頭に設計されました。 これはRSpec 2でドキュメント化されましたが、実行時に常にこれを明示的に強制しているわけではありませんでした、そして我々はときどき、 ユーザーがrspec-mocksの機能をテストごとのライフサイクルの外から使おうと試みたときについてのバグレポートを受け取りました。

RSpec 3では、我々はこれをより厳しくし、このライフサイクルを実行時に明示的に強制します:

  • rspec-mocksの機能をbefore(:context)フック(あるいはcurrent exampleが存在しない他のどのcontext)から使う使い方はサポートされません。
  • テストダブルは1つのexampleにしか使えなくなりました。もしあなたがテストダブルをそれが作られたexampleの外から使おうとこころみた場合(e.g. うっかりそれをクラスアトリビュートに代入して、それ以降のexampleでそれを使った場合)、あなたはエラーをうけとるでしょう。

あと、我々はあなたに任意の場所で一時的なスコープをつくれるような新しいAPIを提供します(例えばbefore(:context)フックとか):

my_web_crawler_spec.rb

describe MyWebCrawler do
  before(:context) do
    RSpec::Mocks.with_temporary_scope do
      allow(MyWebCrawler).to receive(:crawl_depth_limit).and_return(5)
      @crawl_results = MyWebCrawler.perform_crawl_on("http://some-host.com/")
    end # ブロックが完了すると検証とリセットが起こる
  end

  # ...
end

これらの変更を実装し、たすけてくれてありがとうSam Phippen、 そしてwith_temporary_scopeの機能を提案してくれてありがとうSebastian Skałacki。

より詳しい情報:

any_instanceのインプリメンテーションブロックがレシーバを渡す

メソッドスタブにインプリメンテーションブロックを提供することは、オブジェクトの状態に寄るような計算を行うのに役にたつでしょう。 RSpec 2でany_instanceを使うとき、残念ながらシンプルにこれを行う方法はありませんでした。 RSpec 3では、レシーバーは最初の引数としてany_instanceのインプリメンテーションブロックにわたってくるので、これを簡単に行うことができます:

any_instance_example.rb

allow_any_instance_of(Employee).to receive(:salary) do |employee, currency|
  usd_amount = 50_000 + (10_000 * employee.years_worked)
  currency.from_usd(usd_amount)
end

employee = Employee.find(23)
salary = employee.salary(Currency.find(:CAD))

これを実装してくれてありがとうSam Phippen。

より詳しい情報:

rpsec-rails

ファイルタイプに基づく推測がデフォルトで無効になりました

rspec-railsはspecファイルのファイルシステム上の場所にもとづき自動的にspecにメタデータを追加します。 これは新規ユーザーに混乱をもたらします、そしてこれを好まないベテランのユーザーもいます。 RSpec 3では、この動作を明示的に有効にするようになりました:

spec/spec_helper.rb

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

流行しているチュートリアルではこの動作を前提にしているため、デフォルトで生成される設定ファイルはこれを有効にしたままです。 自動的な推測を使わずに明示的にspecにタグ付けするためには、typeメタデータを設定してください:

things_controller_spec.rb

RSpec.describe ThingsController, type: :controller do
  # spec/controllers以下に置くのと同じ意味
end

使用できるtypeの違いについては、それぞれのspecの型ごとにドキュメント化されています、たとえばcontroller specsのためのドキュメントなど。

より詳しい情報:

activemodelのサポートが切りだされました

mock_modelとstub_modelがrspec-activemodel-mocks gemに切りだされました。

切り出してくれて、そして新しいgemのメンテナンスを申し出てくれてありがとうThomas Holmes。

webratのサポートがなくなりました

Webratのサポートがとりのぞかれました。変わりにcapybaraを使ってください。

匿名controllerのカイゼン

rspec-railsは長い間、あなたにテスト用の匿名のコントローラを作ることを可能にしてきました。 RSpec 3では、それらはいくつかのカイゼンを施されました:

  • デフォルトで、匿名コントローラはApplicationControllerを継承せず、説明している対象のクラスを継承するようになりました。この動作はinfer_base_class_for_anonymous_controllersの設定オプションで無効にすることが出来ます。
  • 標準的ではないコンテキストでそれを使ったときについて多くのバグフィックスが行われました、たとえば抽象的な親やApplicationControllerを継承しなかった場合など。もしあなたが過去に匿名コントローラ絡みの問題を経験しているなら、今が再度それを使ってみるいい機会です。

より詳しい情報:

最後に

いつものように、全てのchangelogsはそれぞれのサブプロジェクトで見ることができます:

RSpec 3は、ここ4年ではじめてのメジャーリリースです。 この大仕事は大勢のコントリビュータによって成されました。 あなたがRSpecをどう使おうとも、我々はあなたが新しい変更を我々以上に気に入ってくれることを願っています。

このブログ記事を書くのを助けてくれてありがとうXavier Shay、Jon Rowe、校正をありがとうSam Phippen、Aaron Kromer