読者です 読者をやめる 読者になる 読者になる

RSpecのベストプラクティスとRSpec3の新機能

ruby rspec

WEB+DB PRESS Vol.89 の いまどきのRSpecテスト が良かったです。
日本人で唯一のRSpec core teamに所属するyuji nakayamaさんの記事ですので読み応えがあります。
余談ですがcore teamの中でもアイコンが目立っています。かっこいい。
https://github.com/rspec

WEB+DB PRESS Vol.89

WEB+DB PRESS Vol.89

RSpecのベストプラクティス

おすすめ spec/spec_helper.rb

テストのrandom実行以外はやっていませんでした。いろいろ捗ります。

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.syntax = :expect # should記法の無効化
  end

  config.mock_with :rspec do |mocks|
    mocks.syntax = :expect # should記法の無効化

    # allow(obj).to receive(:message)構文利用時に、存在しないメソッド名を指定した場合
    # エラーとする。typo時に有効。
    mocks.verify_partial_doubles = true
  end

  config.filter_run :focus  # :focus の利用
  config.run_all_when_everything_filtered = true # :focus 指定なしの場合全てのテストが実行される

  # --only-failures, --next-failuresを利用するためのRSpecの実行結果を保存する場所。gitignoreに追加すると吉
  config.example_status_persistence_file_path = 'spec/examples.txt'
  config.order = :random # testのrandom実行
  Kernel.srand config.seed # RSpecの--seedオプションの値によって一意に決まるようになる。乱数利用時のdebugに便利
end

fitが便利

:focus => true を記述することで、特定のテストだけ実行できる機能があります。

it "does another thing", :focus => true do
  # do some test
end

describeでもitでも書けて便利なんですが、":focus => true" って書くのが面倒なので
利用したことがなかったのですが、なんとショートカットが用意されていました。
 
it => fit に変更するだけで同じ機能が利用できます。最近書いたテストでは多用しました。こりゃ便利。

fit "does another thing" do
  # do some test
end

.rspec

rspec --init時に以下の内容が追加されていました。
なんと、これのおかげで require 'spec_helper' が不要になっていました。

--require spec_helper

・クラスメソッドごとに階層化する
 describeでメソッド単位に分け、#method_name とする。

・letとsubjectを使い、beforeフックを避ける  ・インスタンス変数はtypo時にエラーにならない。
 ・beforeフックは遅延実行されない
 ・複数の変数間の依存関係とフックの実行順序の整合性を取る必要がある

・itブロック内の処理を最低限に保つ

・shared_examples / shared_context 利用時にはブロックパラメータを利用する
 letを利用すると暗黙的に依存した形となるので、ブロックパラメータを利用し明示する。

RSpec3の新機能

and / or も紹介されていましたが、これは理解していたので省略。
いまいち良くわかっていなかった、新しいdoubleの記法についての説明が良かったです。

インタフェース検証付きテストダブル

従来の double('name') ではメソッドtypoした場合でもエラーとならずに
気づかない可能性がありましたが、RSpec3より導入された
instance_double、class_double、object_double を利用することで検証できるようになりました。
 
instance_doubleは、検証対象となるクラス定数かクラス名の文字列を渡すことで、
そのクラスのインスタンスのテストダブルを作成できる。
class_doubleの引数も同様で、そのクラス自身のテストダブルを生成できる。
object_doubleには、クラスではなく検証対象のインスタンスそのものを渡す。
BasicObject#method_missingを使ったものや、動的なメソッドを生成するオブジェクトの 代替として使うのに適している。
 
instance_doubleは、Module#instance_methodを利用してメソッドの応答性を検証している。
object_doubleは、Object#respond_to? を使って検証を行うので
#respond_to? や #respond_to_missing を実装していれば、正常に検証できる。

複数の失敗を回収する aggregate_failures

requestを実行するようなテストで複数のexampleを実行したいケースを想定しとき
それぞれexampleを分けると、テストの実行時間が掛かってしまうという問題がありました。
また、分けずにitの中に複数exampleを書くと、途中で失敗した際にそれ以降のテストが実施されないという問題がありました。

aggregate_failuresブロック内に記述したエクスペクテーションは、
失敗しても処理が中断されず、例外オブジェクトは回収されます。
ブロック内の処理が全て終わったタイミングで1つでも失敗があれば、
回収された失敗があらためて例外としてあげられ、exampleが失敗します。