RSpec について勉強したまとめ
これは何?
会社で RSpec を使った、BDD を行っているのて理解の為にまとめてみました
日本語でおk
BDDの歴史
XPのテスト駆動開発(TDD)
↓
テストにより、逆に仕様が明確化
↓
テストってある意味要求仕様と同一だよね
↓
自然言語で書かれれば、いろいろ便利だよね
↓
DSLの開発(RSpec等)
↓
BDD
BDDだと何が得?
TDDより、テストコードが可読性に優れている(自然言語(英語)に近い)事
具体的にはどんな感じなの?
こんな感じ
テストされるコード
class TestClass def addition(a,b) return a+b end def evenNumber?(a) return a.to_i % 2 == 0 end end
Specのコード
# describeメソッドにて、ひとまとまりの振る舞いを定義 describe TestClass, "が、行う処理" do before(:all) do # 事前処理(describe毎に1回呼ばれる) end before do # 事前処理(example毎に1回呼ばれる) @testClass = TestClass.new end # example(実行可能なサンプル) # exampleの中身(〜 should … の事をエクスペクテーションと呼ぶ) it "は、足し算が正しく行われる事" do @testClass.addition(1,2).should == 3 end it "は、偶数奇数を、判別できること" do @testClass.evenNumber?(2).should be_true @testClass.evenNumber?(1).should be_false end after do # 事後処理(example毎に1回呼ばれる) end after(:all) do # 事後処理(describe毎に1回呼ばれる) end end
実行結果(正常)
# fsで、レポート形式で出力 # c で色がつく spec -fs specDemo.rb TestClass が、行う処理 - は、足し算が正しく行われる事 - は、偶数奇数を、判別できること Finished in 0.447497 seconds 2 examples, 0 failures
describe
ひとまとまりの振舞を記載
書き方は以下の3通り
- describe "説明"
- describe 対象のクラス, "説明"
- describe 対象のクラス
describeのブロック内に具体的なexampleを書く
example
振舞における個々の項目を記載
it メソッドにて定義
メソッド名が it であることで、以下のように自然文(英語)に近く、可読性が高い文が書ける
it "should 〜(は、〜であること)" do … end
エクスペクテーション
example のブロックに記述される、プログラムに期待する振舞
should メソッドにて定義
should メソッドは、Object クラスに追加されるので、あらゆるインスタンスで使用可能
should メソッドの引数にはマッチャと呼ばれる、期待する振舞を表すオブジェクトを渡す
マッチャの例
# 演算子マッチャ (1+2).should == 2 # 述語マッチャ # be_xxx ならば、対象のインスタンスの xxx? メソッドが呼ばれて、trueならばパス # 下記の場合、test.empty? が呼ばれる test.should be_empty # 例外 # 例外が発生する箇所を、lamdaでくくる必要がある lamda{ test.test_method() }.should raise_error(StandardError, 'errorMsg') # その他 test.should be_nil test.should be_true test.should_not be_false
この辺も参考になりそう(ちょっと古いらしいけど)
特殊なエクスペクテーション
violated メソッド
exampleで使用
必ずテストが失敗する
テストが未実装である事を表明
it "は、足し算が正しく行われる事" do violated "まだテスト未実装" end
pending メソッド
現在は例外が発生するけども、それはペンディングだよ。という表明で使用
例外が発生する部分をブロックにする
ブロックにした場合は、例外が発生しないと、例外が発生する(ややこしい…)
it "は、足し算が正しく行われる事" do pending("ここは保留") do @testClass.addition(1,2).should == 3 end end it "なにか理由" do # do をつけないと、以降が全て pending となる pending("以降保留") do … end
モックとスタブ
・スタブは、テスト時の呼び出しに対して、あらかじめ用意された結果を返す。 通常、テスト用にプログラムされたところ以外には応答しない。 スタブは呼び出しの情報を記録することもある。 例えば、Eメールゲートウェイスタブは「送られた」メッセージを記録するような場合だ。 単に「送られた」メールの数を記録する場合もあるだろう。 ・モックは、エクスペクテーションが事前にプログラムされたものである。 エクスペクテーションとは、受信する一連の呼び出しの仕様を表わしたものである。 期待されない呼び出しが行なわれた場合は例外をスローする。 また、テスト実行後の検証 (verification)で、期待された呼び出しがすべてきちんと行われたかどうかを確認する。
Martin Fowler's Bliki in Japanese - テストダブル
- スタブは、固定値を返却する
- モックは、振舞が定義されている
らしい。いまいち分かりきれていない
使い道・利点
- 個々のクラスやオブジェクトを完全に分離して、テストできる
- そのようにするため、各クラスの依存性が下がる
- 各クラスのAPIがきれいになる
スタブメソッドと、shoud_receive について勘違いがあるので、後日修正します
RSpecでの実例
具体的にはこんな感じ
describe "MockSample" do before do # mockの定義 @mockTest = mock('MockTest') end it 'は、mockが正しく動作すること' do # shoud_receive でスタブメソッドを定義 # with で、メソッドの引数を定義 # 引数が固定値文字列ならば、それを指定 # and_returnで戻り値を定義 @mockTest.should_receive(:test).with(:test).and_return('OK') # 型の固定(an_instance_of) # and_returnがブロックの場合、その結果を返す @mockTest.should_receive(:test2).with(an_instance_of(String)).and_return{|a| a} # and_raiseで例外をreturn # any_number_of_timesで、何度も呼び出し可能(回数を指定しないと、1回しか呼べない) @mockTest.should_receive(:test3).any_number_of_times.and_raise("mockError") @mockTest.test(:test).should == 'OK' @mockTest.test2('testtest').should == 'testtest' lambda{ @mockTest.test3() }.should raise_error(StandardError, 'mockError') lambda{ @mockTest.test3() }.should raise_error(StandardError, 'mockError') end end