RSpecを0.8.x から 1.0.x にあげる際のポイントメモ

RSpecは長らく0.Xリリースが続いており、APIの変更がなされていました。

しかし、2007/5/19に正式りリースである1.0.0が公開され、現在ではそのバージョンが活発にメンテナンスされています。
機能の追加はもちろん、バグ修正も多く行われており、またRSpecの開発陣も、このバージョンのAPI後方互換性を保っていくことを表明しています。これから使いはじめる分には「テスト基盤がころころ変わる」問題は多少は収まることと思います。このへんを懸念してRSpecを導入していない方がいらしたら、このバージョンは是非さわってみてください。

ということで、実際に0.8.xで書いていたspecを1.0.0互換にしてみました。完全に1.0.xスタイルの書き方というわけでは鳴く、あくまで既存資産分を動作させるための最小限の変更、ということに御注意ください。

注意点

contextとspecify

引き続き使用可能。移行時の修正は不要。ただし、今後テストを追加する場合には describeとitを使用するべき。

context_setupやcontext_teardown

context_setupとcontext_teardownが使えなくなった。それぞれ before(:all)とafter(:all)を用いれば良い。

また、setupとteardownは引き続き使用可能であるため、移行時の修正は不要とする。これもcontext/specifyと同様にこれから追加するテストに関してはbefore/afterを使用する。(このへんはプロジェクト次第ですが、移行の1stステップでは手を広げすぎないほうがよい)

should_xxx の挙動

0.8.xまではshould_xxx(args)という形で1メソッドでアサーションを書いていた(正確にはmethod_missingで/should_/を引っかけていた)が、今後はshould(matcher)ないしshould_not(matcher)のみを用いることになる。

matcherとは

shouldの引数となるmatcherについては、下記のようなルールとなる。

  • 検査対象のオブジェクトに真偽値を返すメソッドがある場合(例: Array#empty?)は、be_の後ろに真偽値メソッドから"?"をのぞいたものを付ければ良い。
    • beの後ろにはあいかわらずaやanを付けたり付けなかったりできる。
    • 真偽値を返すメソッドのうち、引数が必要なものはbe_method(args)という書き方になる。
  • いくつかの特殊なmatcher生成メソッドがある。
    • have
    • raise_error(旧 should_raise Exception)
    • change
 # 旧
 @target.should_be_empty
 # 新
 @target.should be_empty

 # 旧
 @target.should_eql another
 # 新
 @target.should eql(another)

 # 旧
 @target.should_not_be_an_instance_of Array
 # 新
 @target.should_not be_an_instance_of(Array)

 # 旧
 @target.should_have(3).items
 # 新
 @target.should have(3).items

よくエラーになる例

今回の移行で特にたくさんエラーとなったパターンです。

Proc#should raise_error(Exception)

ブロックを実行すると例外が発生することの検証。もちろんshould_notで発生しないことも検証できる。

 # 旧
 @proc.should_raise( Exception )
 # 新
 @proc.should raise_error( Exception )

raiseが予約語になっているためraise_errorでmatcherを作成している。多少わかりづらい。

Proc#should change(obj, methods).by(delta)

ブロックを実行すると引数に渡したobj#methodの値がdeltaだけ変わることを検証する。

describe Array, "<<" do
  before do
    @arr = [1,2,3]
  end
  it "should change @arr.size by 1" do
    lambda{ @arr << 3 }.should change(@arr, :size).by(1)
  end
end

上記は現行で動作する書き方。以前との変化はこんな感じ。

 # 旧
 @proc.should_change(@arr, :size).by 1
 # 新
 @proc.should change(@arr, :size).by(1) # カッコが複雑だが、普通にSyntaxErrorをつぶせていればOK
RSpec on Rails 関係

別に書きます。

RSpec on Rails に関する仕様変更

モデルは従来の機能がそのまま使える(上記のshouldの使用変更への追従は必要)。
コントローラに関しては多くの変更がある。

リダイレクトの書き方

これまではリダイレクトはコントローラに対して記述していた。またmockのような記述であったためリクエストの前に実行する必要があった。

context do
  controller_name :foo
  setup do
    controller.should_redirect_to(:actioon => xxx)
    get "/bar"
  end
end

今後はresponseオブジェクトに対して検証する。また、リクエストを実行したあとに記述できる。

describe do
  controller_name :foo
  before do
    get "/bar"
  end
  it "should redirect to foo/bar" do
    response.should redirect_to(:action => xxx)
  end
end
RSpecで仕様できるテストメソッドの拡張

これまではSpec::Rails::Runner::EvalContextに直接メソッドを追加していた。

module Spec::Rails::Runner
  class EvalContext
    def skip_filter(*filters)
      filters.each do |filter|
        controller.should_receive(filter).and_return(true)
      end
    end
  end
end

しかし、Spec::Rails::DSL::ControllerBehaviourHelpers::ExampleMethods に追加するほうが副作用が少なそうである。

module Spec::Rails::DSL
  module ControllerBehaviourHelpers
    module ExampleMethods
      def skip_filter(*filters)
        filters.each do |filter|
          controller.should_receive(filter).and_return(true)
        end
      end
    end
  end
end

詳しくは vendor/plugins/rspec_on_rails/lib/spec/rails/dsl/behaviour/controller.rb を参照すること。簡単にいうと、EvalContextは実行するspecのパスに基づき、特殊なEvalContext(モデル用、コントローラ用、ビュー用)を作成している。
ちなみに、実際の生成はSpec::DSL::BehaviourFactoryによって行われる(Factory自体ははRSpec本体の機能)。

上記 ExampleMethods モジュールは ControllerBehaviourHelpers.included()フックによって、実行時にControllerEvalContextにmix-inされるため、上記のように定義したヘルパーメソッドが使えるようになる。
ちなみに、Behaviourレベル(itの外、describeのすぐ中)で使いたいヘルパーについてはSpec::Rails::DSL::ControllerBehaviourHelpers::BehaviourMethodsで定義すれば良い。