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

おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Chapter 13 Spec::Expectations その2

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を委譲する。

Implicit Subject

newメソッドかつ引数なしでインスタンスを作る場合、subjectメソッドすら省略できる。

describe RSpecUser do 
  it { should be_happy }
end

これまで扱ってきた省略は便利だけど、それを使うことを目的にしてはいけない。あくまで条件がそろったときだけ使いましょう。