Chapter 13 Spec::Expectations - おもしろWEBサービス開発日記の続き。
13.3 Predicate Matchers
array.empty?.should == true
みたいに書くのはいまいち見た目が良くない。
array.should be_empty
Rspecでは上記のようにも書けるようにした。これは全ての末尾に"?"をもつメソッドで使える。上記のシンタックスシュガーを実現するために、RSpecはmethod_missingをオーバライドしている。be_で始まるメソッドでRSpecは"be_"を取り除き、"?"を加えてsendしてる。
be_を付けただけではまだちょっと読みづらいメソッドがある。例えばinstance_of?(type)はbe_instance_ofになる。もう少し読みやすくするために、RSpecはbe_a_とbe_an_とも書けるようにしている。それにより
person.should be_a_kind_of(Player) person.should be_an_instance_of(Pitcher)
とか書ける。
これだけやってもいまいちな場合がある。例えば、parser.should be_can_parse("some text")とか微妙。そういうときはカスタムマッチャを書くとよいらしい。
13.4 Be true in the eyes of Ruby
RSpecの be_true と be_false マッチャは厳密にtrueかfalseか見るわけではなくて、例えばnilならbe_falseでおk。
厳密にtrueかfalseかみたいときは
true.should equal(true)
のようにするといい。
13.5 Have Whatever You Like
Hash.has_key?(:foo).should be_true
だとださいので
Hash.should have_key(:foo)
と書きたい。
RSpecはmethod_missingを使ってhave_で始まるマッチャはhas_...?に変換するようにしている。have_...を使うとフィードバックがより表現豊かになる。have_を使わないと
expected true, got false
使うと
expected #has_key?(:foo) to return true, got false
has_...?な全てのメソッドでこの変換は使える。
Owned Collection
collectionを持つオブジェクトの振る舞いを調べたい場合。
field.players.select {|p| p.team == home_team }.length.should == 9
は下記のような感じで書き換えられる。
home_team.should have(9).players_on(field)
haveはマッチャ。players_onメソッドをhome_teamに委譲する。players_onメソッドがhome_teamにも定義されていない場合、NoMethodErrorになる。そのメソッドの結果がlengthかsizeメソッドを持っていない場合も同じ。結果が期待していた数と異なる場合はエラーではなくfailedになる。
Un-owned Collections
自分自身がcollectionの時は
collection.should have(37).items
のように書く。itemsはこの場合では純粋なシンタックスシュガー。
String
Stringもcollectionの一つ。Arrayとは違うけど、collectionと同じメソッドがたくさんある。lengthもsizeもある。文字列の長さを知りたいときにはhaveが使える。
"this string".should have(11).characters
この例ではcharactersはitemsと同じシンタックスシュガー
Precision in Collection Expectations
下記のように最小と最大のようなマッチャも書ける。
day.should have_exactly(24).hours dozen_bagels.should have_at_least(12).bagels internet.should have_at_most(2037).killer_social_networking_apps
have_exactlyはhaveの単なるエイリアス。
How It Works
haveはいくつかの異なったシナリオを扱える。haveメソッドの戻り値は Spec::Matchers::Have のインスタンスで、引数として渡された数値で初期化される。
例
result.should have(3).things
これは下記の表現と同じ
result.should(Have.new(3).things)
まずHave.new(3)を評価して、新しいHaveクラスのinstanceを作り、引数(3)を格納する。次に、RubyインタプリタはthingsをHaveオブジェクトに送る。Haveオブジェクトはthingsメソッドを持っていないのでmethod_missingが実行される。Haveはmethod_missingをオーバライドしていて、後で使うためにメソッド名を格納(このばあいはthings)する。そしてselfを返す。
続いて下記のようなコードが実行される
have.matches?(result)
- targetオブジェクト(result)がthingsメソッドを持っていたらそれが実行される。実行結果のオブジェクトがlengthかsizeを持っていたらそれをみる。両方持っていたらlengthをみる。
- method_missingで格納したメソッドをターゲットオブジェクトが持っていなかったら、自分自身がlengthかsizeを持っているか調べる。この場合、method_missingで格納したメソッド名は無視される。
- target オブジェクトはlengthかsizeメソッドを持っていれば何でもいい。collectionでなくてもいい。Stringでもいい。"this string".should have(11).characters
- lengthとsizeを別の意味で持っているカスタムcollectionがあったときに、期待していない結果になるかもしれない。
13.6 Operator Expressions
下記のようにも書ける
result.should =~ /some regexp/ result.should be < 7 result.should be <= 7 result.should be >= 7 result.should be > 7
13.7 Generated Description
specifyメソッドをitの代わりとして使っている
docstringが無いときにより見やすい。itとspecifyはexampleメソッドのエイリアス。
Rspecのマッチャは自身のdescriptionを生成する。docstringが無いときは最後のdescriptionsを使う。
自動で生成されるdocstringと、手動で指定するのとうまく使い分けるのがいいみたい。
13.8 Subject-ivity
評価するオブジェクトが決まっているような場合に、subjectメソッドをbeforeブロックの代わりに使うことでexampleを簡略化できる。
下記のように明示的にshouldのレシーバとしてsubjectを使う方法と
describe Person do subject { Person.new(:birthdate => 19.years.ago) } specify { subject.should be_eligible_to_vote } end
下記のようにレシーバを省略する方法とがある。
describe Person do subject { Person.new(:birthdate => 19.years.ago) } it { should be_eligible_to_vote } end
上記の例ではshouldメソッドのレシーバがないので、example自身がshouldメソッドを受け取る。するとexampleはsubjectを呼び出して、shouldを委譲する。