2011年7月5日火曜日

RSpec 簡潔に記述する(1) it ブロックを短く書く!

コード比較


describe User do
  it "should be instance of User" do
    Factory.create(:user).should be_instance_of(User)
  end

  it "should belongs to Guild" do
    Factory.create(:user).guild.should be_instance_of(Guild)
  end

  describe "#add_exp(experience_point)" do
    it "should not raise error" do
      lambda{Factory.create(:user).add_exp(10) }.should_not raise_error
    end
  end

  describe "::get_list()" do
    it "should be instance of Array" do
      User::get_list().should be_instance_of(Array)
    end
  end
end


describe User do
  subject { Factory.create(:user) }
  it { should be_instance_of(User) }
  its(:guild){ should be_instance_of(Guild) }

  describe "#add_exp(experience_point)" do
    subject { lambda{Factory.create(:user).add_exp(10) } }
    it { should_not raise_error }
  end

  describe "::get_list()" do
    subject { User::get_list() }
    it { should be_instance_of(Array) }
  end
end

新旧を比較すると、新しい方は it メソッドが 1 行で書かれており、音読すれば意味が通じる内容と成っているのがわかると思います。(ex, it should not raise error)

それでは、どのようにして簡潔にしていくか、 4 ステップで見ていきましょう。

1. describe ブロック中の subject を定義する

subject メソッドを使い、この describe ブロックで、一体何の spec を書きたいのかを明確に記述します。

subject を定義することで、各 spec 中で subject を参照出来る様になります。

早速書きなおしてみましょう。

describe User do
  subject { Factory.create(:user) } # このブロックは、 User のインスタンスを検証する

  it "should be instance of User" do
    subject.should be_instance_of(User)
  end

  it "should belongs to Guild" do
    subject.guild.should be_instance_of(Guild)
  end

  describe "#add_exp(experience_point)" do
    # このメソッドの実行結果を検証する
    subject { lambda{Factory.create(:user).add_exp(10) } }

    it "should not raise error" do
      subject.should_not raise_error
    end
  end

  describe "::get_list()" do
    # このメソッドの返す値を検証する
    subject { User::get_list() }

    it "should be instance of Array" do
      subject.should be_instance_of(Array)
    end
  end
end

2. subject は省略できる

ぶっちゃけ、subject がなくても、 it メソッドは適当に解釈してくれます。

もとい。もともと、 it メソッドにおいて暗黙のレシーバーが  subject です。

describe User do
  subject { Factory.create(:user) }

  it "should be instance of User" do
    should be_instance_of(User)
  end

  # ここはアトで。
  it "should belongs to Guild" do
    Factory.create(:user).guild.should be_instance_of(Guild)
  end

  describe "#add_exp(experience_point)" do
    subject { lambda{Factory.create(:user).add_exp(10) } }

    it "should not raise error" do
      should_not raise_error
    end
  end

  describe "::get_list()" do
    subject { User::get_list() }

    it "should be instance of Array" do
      should be_instance_of(Array)
    end
  end
end

3. (単純なら) 記述は省略でき (自動生成でき) る。

RSpec では、-fs オプションを付けることで it メソッドのコメントを表示させることが出来ますが、各 spec 中で、出てくる should が 1 つ程度でしたら、 RSpec 自身によって仕様を自動記述出来ます

it メソッドでコメントを省略すると、spec 中から無理やり記述が自動生成されます。

さらに、単純な spec なら 1 行程度なので do - end じゃなくて {} 使っても、あんまり見づらくなりませんので、どんどん省略しちゃいましょう。

逆に言えば、自動生成出来る程度の粒度で 1 つの spec を書くべきです。粒度は細かく。量は多めに!


describe User do
  subject { Factory.create(:user) }
  it { should be_instance_of(User) }
  # ここはアトで。
  it "should belongs to Guild" do
    Factory.create(:user).guild.should be_instance_of(Guild)
  end

  describe "#add_exp(experience_point)" do
    subject { lambda{Factory.create(:user).add_exp(10) } }
    it { should_not raise_error }
  end

  describe "::get_list()" do
    subject { User::get_list() }
    it { should be_instance_of(Array) }
  end
end


4. プロパティや簡単なメソッドとかは its で。

プロパティとか、プロパティっぽく振舞わせるメソッド (正確には、ruby の getter は全部メソッドだけど。。。) は、
it じゃなくて、its を使うと簡潔に書けます。 ただし、あくまでも簡単なプロパティ (の getter) 及びメソッド用です!

subject 自体の spec を書くなら、 it メソッド。subject のプロパティの spec なら its メソッドですな。

書き方は
its(:property_name){ should == true }

見てわかるとおり、

* describe ブロックで定義した subject ブロックの値 と it メソッド内の subject は同じ。
* (describe ブロックで定義した subject ブロックの値).property_name と its メソッド内の subject は同じ。
* property_name メソッドに引数を与えたい? それはムリだから、そういう場合はおとなしく子供の describe ブロックを書こう。

と、なります。


describe User do
  subject { Factory.create(:user) }
  it { should be_instance_of(User) }
  its(:guild){ should be_instance_of(Guild) }

  describe "#add_exp(experience_point)" do
    subject { lambda{Factory.create(:user).add_exp(10) } }
    it { should_not raise_error }
  end

  describe "::get_list()" do
    subject { User::get_list() }
    it { should be_instance_of(Array) }
  end
end
次は「describe と context」「let」について書くことにします。