「RSpecの構文」の編集履歴(バックアップ)一覧はこちら
RSpecの構文 - (2010/01/24 (日) 14:19:49) の最新版との変更点
追加された行は緑色になります。
削除された行は赤色になります。
* RSpecの構文
見本は、これ
http://github.com/mitim/tddbc-lrucache/blob/master/lru_cache_spec.rb
** 慣習
RSpec用のテストとして書くテストコードは、[テスト対象のファイル]_spec.rb という名前でつくる。
** なにはなくともrequire
require 'lru_cache'
テスト対象のファイルを読み込ませる。
ちなみに、RSpecの何かをrequireする必要なない。
** まずは基本
describe do
end
で、一番外側のブロックを記述する。
通常は、次のようにテスト対象のクラスを宣言しておく。
describe LRUCache do
end
また、一緒に説明を付けることも可能。
describe LRUCache, "を初期化する場合" do
end
もちろん、説明だけにすることも可能。
describe "LRUCacheのケース" do
end
** describeの中
*** describeの中にもdescribeを重ねられる
たとえば、同じクラスのテストでも、初期化のテストをがっつりやって、次にhogeメソッドのテスト、そしてfugaメソッドのテストを…とやっていくと、必然的にテストが長く見づらくなってくる。
たとえば、hogeメソッドのテストとfugaメソッドのテストとでは、前準備で必要なものがぜんぜん違う。
そんなときには、describeのなかにさらにdescribeを書いて、整理をつける。
*** describeの説明文
ここに何を書くべきか。
自然に仕様書っぽく構成した文書にしたい場合、次のように気をつけて記述してみるといい。
[クラス名], [て/に/を/は/の]○○する場合(ケース)
※クラス名は、ひとつ上のdescribeでまとめてしまった方が記述がスッキリするのは、言うまでも無い。
*** テストの前準備 before
テスト(it)を実行する前に必要な、テストと直接は関係ない準備のための処理を記述する。
たとえば、テスト対象のオブジェクトを生成して、インスタンス変数に入れたり。
たとえば、モックやスタブを用意して、本物のオブジェクトと摩り替えたり。
たとえば、ファイルを用意したり。
before :each do
end
before :all do
end
:eachを指定したbeforeは、各テスト(it)のたびに、その前に必ず実行される。
:allを指定したbeforeは、describeの最初に一度だけ実行される。
*** テストの後処理 after
テスト(it)を実行した後に必要な、テストと直接は関係ない後片付けのための処理を記述する。
after :each do
end
after :all do
end
:eachを指定したafterは、各テスト(it)を実行するたびに、その後に必ず実行される。
:allを指定したafterは、describeの最後に一度だけ実行される。
** itの中
テストのコードは、すべてitの中に記述する。
基本的な書き方は、次のとおり。
[テスト対象オブジェクト].[テスト対象メソッド].should == [結果]
*** itの説明文
ここに何を書くべきか。
自然に仕様書っぽく構成した文書にしたい場合、次のように気をつけて記述してみるといい。
[どのような操作をする]と、[その結果はどうなる]。
**** 基本的な機能要件を説明する場合
は、○○すると、××になる。
**** 特殊な機能要件や、エラー的な機能要件を説明する場合
もし、○○すると、××になる。
*** shouldメソッド
shouldメソッドは、そのオブジェクトの状態を確認し、指定された状態であるか否か(~であるべき)を検査する。
== 演算子のほか、be系のMatcherが多数用意されている。
全てのオブジェクトに動的に加えられたメソッドなので、基本的には何でも検査可能。
*** should_notメソッド
shouldと違い、こちらは否定検査(~であってはいけない)をするときに使用する。
*** Matcher群
shouldで検査できるよう、多数のMatcherが用意されている。
: == expected|==比較の結果が同じか
:be_true|真であるか
:be_false|偽であるか
:be_nil|nilか
:be_empty|Arrayが空か
:be_an_instance_of Class|クラスがClassと一致するか
:be_a_kind_of Class|クラスが指定Class、もしくはそのサブクラスか
:have_key key|keyがあるか
:be_close E,D|数値が、E~Dの範囲に収まっているか
:change receiver,message,&block|Procオブジェクトが変化するか
:change(receiver,message,&block).by value|Procオブジェクトが指定された値で変化するか(should_notは使用できない)
:change(receiver,message,&block).from(before).to(after)|Procオブジェクトがbeforeからafterに変化するか(should_notは使用できない)
:eql expected|==とほぼ同義((eql?で比較)
:equal expected|同じオブジェクトか
:have(n).items|配列などのコレクションオブジェクトが、n個の要素を持っているか。
:have_exactly(n).items|配列などのコレクションオブジェクトが、ちょうどn個の要素を持っているか。(should_notは使用できない)
:have_at_least(n).name|配列などのコレクションオブジェクトが、n個以上の要素を持っているか。(should_notは使用できない)
:have_at_most(n).name|配列などのコレクションオブジェクトが、n個以下の要素を持っているか。(should_notは使用できない)
:include expected|配列などのコレクションオブジェクトに、expectedが入っているか。
:match regexp|正規表現regexpにマッチするか。
:raise_error|例外が発生するか。
:raise_error Expected|Expectedな例外が発生するか。
:raise_error Expected,message|Expectedな例外が、messageを伴って発生するか。
:raise_error Expected,regexp|Expectedな例外が、正規表現にマッチするメッセージを伴って発生するか。
:respond_to method,method,method...|オブジェクトが、指定メソッドを全て持つか。
:satisfy {|e| ...}|ブロックの実行結果(eにテストオブジェクトが渡される)が真になるか。
:thorw_symbol(symbol=nil)|symbolがthrowされるか。
書いてる本人が、使ったことがないMatcherが多数。
*** 例外が発行されたかどうかはどうチェックする?
次のようにすると、例外の捕捉ができ、例外発行チェックができる。
proc{ [ターゲットオブジェクト].[ターゲットメソッド] }.should raise_error(ArgumentError)
** モック機能
* RSpecの構文
見本は、これ
http://github.com/mitim/tddbc-lrucache/blob/master/lru_cache_spec.rb
** 慣習
RSpec用のテストとして書くテストコードは、[テスト対象のファイル]_spec.rb という名前でつくる。
** なにはなくともrequire
require 'lru_cache'
テスト対象のファイルを読み込ませる。
ちなみに、RSpecの何かをrequireする必要なない。
** まずは基本
describe do
end
で、一番外側のブロックを記述する。
通常は、次のようにテスト対象のクラスを宣言しておく。
describe LRUCache do
end
また、一緒に説明を付けることも可能。
describe LRUCache, "を初期化する場合" do
end
もちろん、説明だけにすることも可能。
describe "LRUCacheのケース" do
end
** describeの中
*** describeの中にもdescribeを重ねられる
たとえば、同じクラスのテストでも、初期化のテストをがっつりやって、次にhogeメソッドのテスト、そしてfugaメソッドのテストを…とやっていくと、必然的にテストが長く見づらくなってくる。
たとえば、hogeメソッドのテストとfugaメソッドのテストとでは、前準備で必要なものがぜんぜん違う。
そんなときには、describeのなかにさらにdescribeを書いて、整理をつけることができる。
*** describeの説明文
ここに何を書くべきか。
自然に仕様書っぽく構成した文書にしたいなら、次のように気をつけて記述してみるといい。
[クラス名], [て/に/を/は/の]○○する場合(ケース)
※クラス名は、ひとつ上のdescribeでまとめてしまった方が記述がスッキリするのは、言うまでも無い。
*** テストの前準備 before
テスト本文(it)を実行する前に必要な、テストと直接は関係ない準備のための処理を記述する。
たとえば、テスト対象のオブジェクトを生成して、インスタンス変数に入れたり。
たとえば、モックやスタブを用意して、本物のオブジェクトと摩り替えたり。
たとえば、ファイルを用意したり。
before :each do
end
before :all do
end
eachを指定したbeforeは、各テスト(it)のたびに、その前に必ず実行される。
allを指定したbeforeは、describeの最初に一度だけ実行される。
*** テストの後処理 after
テスト(it)を実行した後に必要な、テストと直接は関係ない後片付けのための処理を記述する。
after :each do
end
after :all do
end
eachを指定したafterは、各テスト(it)を実行するたびに、その後に必ず実行される。
allを指定したafterは、describeの最後に一度だけ実行される。
** itの中
テストのコードは、すべてitの中に記述する。
基本的な書き方は、次のとおり。
it "テストの説明" do
[テスト対象オブジェクト].[テスト対象メソッド].should == [結果]
end
*** itの説明文
ここに何を書くべきか。
自然に仕様書っぽく構成した文書にしたい場合、次のように気をつけて記述してみるといい。
[どのような操作をする]と、[その結果はどうなる]。
**** 基本的な機能要件を説明する場合
は、○○すると、××になる。
**** 特殊な機能要件や、エラー的な機能要件を説明する場合
もし、○○すると、××になる。
*** shouldメソッド
shouldメソッドは、そのオブジェクトの状態を確認し、指定された状態であるか否か(~であるべき)を検査する。
== 演算子のほか、be系のMatcherが多数用意されている。
全てのオブジェクトに動的に加えられたメソッドなので、基本的には何でも検査可能。
*** should_notメソッド
shouldと違い、こちらは否定検査(~であってはいけない)をするときに使用する。
*** Matcher群
shouldで検査できるよう、多数のMatcherが用意されている。
: == expected|==比較の結果が同じか
:be_true|真であるか
:be_false|偽であるか
:be_nil|nilか
:be_empty|Arrayが空か
:be_an_instance_of Class|クラスがClassと一致するか
:be_a_kind_of Class|クラスが指定Class、もしくはそのサブクラスか
:have_key key|keyがあるか
:be_close E,D|数値が、E~Dの範囲に収まっているか
:change receiver,message,&block|Procオブジェクトが変化するか
:change(receiver,message,&block).by value|Procオブジェクトが指定された値で変化するか(should_notは使用できない)
:change(receiver,message,&block).from(before).to(after)|Procオブジェクトがbeforeからafterに変化するか(should_notは使用できない)
:eql expected|==とほぼ同義((eql?で比較)
:equal expected|同じオブジェクトか
:have(n).items|配列などのコレクションオブジェクトが、n個の要素を持っているか。
:have_exactly(n).items|配列などのコレクションオブジェクトが、ちょうどn個の要素を持っているか。(should_notは使用できない)
:have_at_least(n).items|配列などのコレクションオブジェクトが、n個以上の要素を持っているか。(should_notは使用できない)
:have_at_most(n).items|配列などのコレクションオブジェクトが、n個以下の要素を持っているか。(should_notは使用できない)
:include expected|配列などのコレクションオブジェクトに、expectedが入っているか。
:match regexp|正規表現regexpにマッチするか。
:raise_error|例外が発生するか。
:raise_error Expected|Expectedな例外が発生するか。
:raise_error Expected,message|Expectedな例外が、messageを伴って発生するか。
:raise_error Expected,regexp|Expectedな例外が、正規表現にマッチするメッセージを伴って発生するか。
:respond_to method,method,method...|オブジェクトが、指定メソッドを全て持つか。
:satisfy {|e| ...}|ブロックの実行結果(eにテストオブジェクトが渡される)が真になるか。
:thorw_symbol(symbol=nil)|symbolがthrowされるか。
書いてる本人が、使ったことがないMatcherが多数。
*** 例外が発行されたかどうかはどうチェックする?
次のようにすると、例外の捕捉ができ、例外発行チェックができる。
proc{ [ターゲットオブジェクト].[ターゲットメソッド] }.should raise_error
** スタブ/モック機能
RSpecには、簡単なスタブとモックを組み込む機構が用意されている。
ここでは、RSpecのスタブ/モック機能に焦点を当てる。
簡単な機構なので、もっとダイナミックな仕組みが欲しい場合は、mochaやflex_mockなどのモック専用ライブラリ/フレームワークを利用した方が効率がいい。
*** スタブとモックの違い
スタブもモックも、UnitTestで必要になる、内部使用している部品をエミュレートすることで、本物の部品の代用となる空箱のようなもののことを指す。
なぜこんなものが必要なのか?それは、こんな理由によっている。
-全てを「本物」でテストしようとすると、「全てが揃わないとテストできない」という本末転倒な事が起こりかねない。
-たとえば時刻に関するオブジェクトのように、システムの構成によって変化してしまうオブジェクトがあると、テスト環境によって差異ができてしまう。
-UnitTestが大きな問題に移ると段々と結合テスト化してしまう、という問題がある。
ある程度のスタブ/モックを使用することで、これらの問題が有機的にクリアされていく。
※ ただし、スタブ/モックを多用し過ぎると、今度はインタフェース不一致の発見を先送りにする、という状況にもなりかねない。このあたりはさじ加減が必要。
では、スタブとモックの違いはなにか?
最近、Martin Fowlerらが、このスタブとモックの違いに関して面白い考察をしていた。この定義は今や一般化しており、RSpecでもこの意味でスタブとモックとを使い分けている。
:スタブ|「インタフェースの定義だけ一致していれば、中身はどのように動いてもいいので、とりあえず用意しておく空箱」のことを言う。
:モック|「インタフェースの定義だけではなく、それがどの様に呼ばれるか、またそれに対して何を返すべきかまでを模倣した空箱」のことを言う。
*** スタブ
何かのクラスのアクセッサをスタブ化するときは、次の構文でアクセッサをスタブに摩り替える。
[クラス].stub!(:[アクセッサ]).and_return([戻り値])
# [クラス]は[アクセッサ]をスタブ化し、[戻り値]を返す。
たとえば次のように書くと、Stringクラスのsizeメソッドが、必ず10を返却するようになる。
String.stub!(:size).and_return(10)
*** モック
何かのクラスのアクセッサをモック化するときは、次の構文でアクセッサをモックに摩り替える。
[クラス].should_recieve(:[アクセッサ])
# [クラス]は[アクセッサ]が呼ばれることを期待する。
[クラス].should_recieve(:[アクセッサ]).and_return([戻り値])
# [クラス]は[アクセッサ]が呼ばれることを期待し、その結果として[戻り値]を返す。
[クラス].should_recieve(:[アクセッサ]).with([引数]).and_return([戻り値])
# [クラス]は[アクセッサ]が[引数]で呼ばれることを期待し、その結果として[戻り値]を返す。
たとえば次のように書くと、Arrayクラスのsliceメソッドが引数(5,5)で呼ばれることを期待し、その結果として[5,6,7,8,9]を返却するようになる。
Array.should_recieve(:slice).with(5,5).and_return([5,6,7,8,9])
----