4 Jun 2012

RSpecのshouldは何をしてるの? RSpecの仕組み

RSpecのshouldはどうやって動いているのか?..という仕組みについてpaperboy&co.の方が既にcodeを読んで解説されているスライドを見つけました。

まずshouldが全てのinstaceで実効可能なのはKernel classに対して定義されているからです。

shouldの居場所
  1. # spec/expectations/extensions/kernel.rb  
  2. def should(matcher=nil, message=nil, &block)  
  3.   Spec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)  
  4. end  

PositiveExpectationHandler#handle_matcherの居場所
  1. # spec/expectations/handler.rb  
  2. match = matcher.matches?(actual, &block)  



RSpecの構造

/lib
└── spec
    ├── expectations
    │   └── extensions
    └── matchers
        └── extensions

expectations/extensionsはその名のとおり拡張を行う。spec/expectations/extensions/にはkernel.rbのみがある

RSpecの構造
spec/matchersには見慣れた名前が。。

ずっとshouldはきっと variable.be_a Stringてかくと variable.is_a? Stringって変えてくれると想像して信じていたけど、実際の条件は逐一Matcherに書いてあるらしい。例えばbe_kind_of matcher
  1. def be_a_kind_of(expected)  
  2.   Matcher.new :be_a_kind_of, expected do |_expected_|  
  3.     match do |actual|  
  4.       actual.kind_of?(_expected_)  
  5.     end  
  6.   end  
  7. end  

be Matcherには僕が想像していたような機能がある

§

Matcherを自作したい時は Object.should be_fine => Object.fine?の法則に当てはまるfine? methodを持つObject定義してあげるか、matches?にrespondするMatcherを書いてmatcher.rbみたいに requireしてあげればいいはず。


§


蛇足だけど

上記 kernel.rbのとおり引数にmessageを渡してfailure時に何が失敗したのかわかりやすくすることもできる

別のshould、Subject#shouldの居場所
これは以下の用に書いたときのshouldにちがいない(と信じてる)
  1. let(:int) { 16 }  
  2. subject { int }  
  3.   
  4. it "should be even" do  
  5.   should be_even  
  6. end  


Change

例えばこんな感じに書いたときは
expect{ array << 42 }.to change{ array.size }.from(0).to(1)
initializeで @value_proc に{ array << 42 }が代入されてfrom methodで@fromに0が、to methodで@to に1が代入される。比較は他のmatchers同様matches?で @beforeと@afterを比較して行われる。event_procには array.sizeが代入される。
  1. @before = evaluate_value_proc  
  2. event_proc.call  
  3. @after = evaluate_value_proc  
スライドすばらしいので全部読むべし