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

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

the rspec book - Chapter 12 Spec::Example その1

The RSpec Bookを読んでの自分用メモです。サブアカの方にメモっていこうかと思いましたが、RSpecの情報はあまり世に出ていないけど需要はありそうな気がしたのでこっちに書きます。意訳と感想がごっちゃになっているし、自分が知ってる情報については省略しているので見づらいかもしれませんがご了承ください。

今のところ本の全部についてメモしようと思っていますが飽きたら終わります。あと順番も適当です。

基礎知識

BDD用の言葉とrspecメソッドの対応でこんがらがるのでメモ

describeメソッド
example groupを定義するメソッド
itメソッド
exampleを定義するメソッド
shouldやmatcha
expectationを定義するメソッド

DSLを使うメリット

RSpecDSLでテストを書いていく。code exampleを(describeやitを使わずに)クラスやメソッドで書くのは可能だけどおすすめしない。

DSLを使うメリットとしては、例えば下記のように文字列で振る舞いを書けること。

it "should match when value < (target + delta)" do
  be_close(5.0, 0.5).matches?(5.49).should be_true
end

Test::Unitだと

def test_should_match_when_value_is_less_than_target_plus_delta
  # ...
end

のようメソッド名に振る舞いを書くのでアルファベット以外(<とか>とか)が使えない。

describeメソッド

describeメソッドは可変の個数の引数とブロックをとり、Spec::Example::ExampleGroupのサブクラスを返す。describeメソッドには普通は一個か二個の引数を渡し、その引数は

  • 状態を定義する前のオブジェクト
  • 期待する振る舞いのサブセット

などを示す。

describeに渡す引数と戻り値の例

describe "A User" { ... } # => A User
describe User { ... } # => User
describe User, "with no roles assigned" { ... } # => User with no roles assigned
describe User, "should require password length between 5 and 40" { ... } # => User should require password length between 5 and 40

最初の引数はクラスかモジュールへの参照、または文字列を渡せる。二つ目の引数は文字列である必要がある(オプション)。

describeのネスト

もしUserがAuthenticationモジュールの中で定義されていたら、下記のように書ける

module Authentication
  describe User, "with no roles assigned" do
  // ...
  end
end

このテストを実行すると下記のような感じで出力される

Authentication::User with no roles assigned

ExampleGroupがモジュールで囲われていると、上記のようにモジュールで囲われたクラスのよう扱われて、出力が見やすくなる。

describeをネストするのも良い(例は省略)。コマンドラインで--format nestedオプションをつけると、出力は下記のような感じでネストされる。

User 
  with no roles assigned
    should not be allowed to view protected content

contextメソッド

contextメソッドはテストコードを見やすくするために定義されたdescribeのエイリアス。下記の二つのコードは同じ意味だけど二つ目の方が見やすくなる。

describe User, "with no roles assigned" do
    it "should not be allowed to view protected content" do
      # ...
    end
end
describe User do 
  context "with no roles assigned" do
    it "should not be allowed to view protected content" do
      # ...
    end
  end
end

上記のように

してcontextメソッドをネストしていくのが一番見やすい気がする。

itメソッド

たいしたこと書いてなかったので省略。

pending Examples

Exampleをpendingする3つの方法について書いてあった。

Exampleをpendingする方法その1 - itメソッドをブロックなしで使う

itメソッドをブロックなしで使うとpending扱いになる。テスト項目を思いついたけどまだ実装しない時に使う。

describe Newspaper do 
  it "should be black" do
    Newspaper.new.colors.should include('black')
  end
  it "should be white" do 
    Newspaper.new.colors.should include('white')
  end
  it "should be read all over"
end
Exampleをpendingする方法その2 - pendingメソッドを使う

他の方法としてpendingメソッドを使う方法もある。このときpendingメソッドより下のコードは実行されない。

describe "onion rings" do 
  it "should not be mixed with french fries" do
    pending "cleaning out the fryer"
    fryer_with(:onion_rings).should_not include(:french_fry)
  end 
end

exampleを実行したくないけどテストの出力に残しておきたいときに使う。

Exampleをpendingする方法その3 - pendingメソッドをブロック付きで使う

pendingメソッドはブロックをとることが出来る。

describe "an empty array" do
  it "should be empty" do
    pending("bug report 18976") do
      [].should be_empty
    end
  end
end

ブロックの中を実行して失敗したりエラーが出たら、RSpecはそのexampleをpending扱いにする。このとき失敗したりエラーがでないとRendingExampleFixedErrorが発生する。これでpendingする必要がなくなったことがわかる。

an empty array
 - should be empty (ERROR - 1)
1) 'an empty array should be empty' FIXED Expected pending 'bug report 18976' to fail. No Error was raised.
pending_fixed.rb:6:
pending_fixed.rb:4:

Finished in 0.007687 seconds 
1 example, 1 failure

テストが通っちゃったときに通知が欲しいときに使う。みたい。具体的なユースケースは思いつかないなー