RSpecのspecの書き方

(5/27: はてブ5人付いてたので、読みやすく直した)

BDDツールとしてRSpecは使えたとしても、実際どうspecを書いていいかで悩むだろう。検索で引っかかるのは、動作解説が中心で、どうspecを書くべきかについてまで触れているものはほとんどない。そこで、物になるspecの書き方を分析してみる。

まず、Exampleを見て分析してみる。

これらを眺めて感じたこと

  • あるまとまった性質ごとに :shared=>true で describeを書く
    • 別途、クラスごとにコンテキストを作るdescribeを書き、it_should_behave_likeで性質記述を取り込む
    • describeはどう分割すればいい?
  • itの記述は"should when "形式
    • (開発者(〜は?)と顧客の応答形式(それは〜したときに〜であるべき)になっている、らしい)
  • 初期化はbefore、終了はafterにまとめる
    • before(:each)、after(:each)ならitごとにbeforeされる
    • before(:all)、after(:all)ならdescribeで一回
  • Mockを手続き的に構築していく
    • どこでmockをつくればいいか?

spec中のdescribe分割

サンプルの分割スタイルは、事前条件ごとにdescribeひとつにまとめ、事後条件はitのタイプごとdescribeにまとめるという感じか。事前条件がセットになってないのは、describe対象は事前条件タイプごとにインスタンスひとつづつあればいいと見ているのだろう。

とりあえず、クラスごとに単に仕様を羅列していた自分のspec記述は、この書きかたをあらためて、よりspec内の構造を意識するよう書き換えたほうがいいと思った。そこで、

この以前書いたまとめ記事も、コンテキストと振る舞いの記述の分割の仕方と、mockの使い方を入れて更新しておいた。

このように、事前条件をコンテキストとした仕様対象のインスタンスと、その振る舞い仕様のセットを分離すると、少し変わったことがおきる。それはmockを使う場合だ。

複数の対象に合うmockの書き方

mockのそれの置き場所が逆転する。つまり、DRY原則に従って共有を進めた場合、mockの結果を事前条件コンテキスト側に置き、mockのインスタンス化を振る舞い側に置くことになるのだ。これって仕方のないことなのだろうか。

そこで、Martin FowlerのMockについての記事:

を読む。ここでは、classic TDDとmockist TDD(≒BDD)を明確に分けていて、mockはSUT(一ユニットテスト)ごとに作る、というのはmockistの流儀のようだ。

ただし、入力がかわれば、付随するassertionも変わるわけで、そうなると入力ごとにassertionの要素(比較対象など)を用意しなくてはいけない。先ほど分割する羽目になったのは、こういった比較対象は入力によって変わるものだからだ。

しかし、この比較対照はジェネリックに生成することもできる。たとえばRSpec説明で使ったArray#foldrの例なら、カウンタは配列の@array.sizeそのものだし、結果も(foldr to_listそのものを外延的に行うことで)処理して生成できる。

# mockのチェックで使う結果の生成
result = "nil"
@array.reverse.each do |item|
  result = "[" + item.to_s + "," + result + "]"
end

こういったものは、ほかのメソッド(foldr結果ではreverse)を使うことになるので、そのメソッドが狂っていたらアウトである。つまり、そのメソッド用のspecも用意しなければいけない。

RSpecでの仕様記述とは?

ところで、例のArrayやStackのようなものはそれ自体全順序的であり、コンテキストはそれにあわせて二つ用意すればよく、振る舞いも同様である。そのためあまり分割したメリットは見られない。

一般的なオブジェクトは要素の直積的な性質があり、(独立した状態)要素にenumがあると網羅するにはその数の積値(状態をフラットにした数)ぶんチェックを用意しなければならない。ただし、振る舞いのほうはたぶん個別の状態要素ごとに書けるので、全網羅するチェック数は積から和に減ることになるだろう。


状態と機能を独立的に記述し、機能の振る舞いは導出的に定義する。こうなるともうほとんど仕様記述言語っぽくなる。ではより完全なチェックがなされる仕様記述言語を使うべきではないのか。

それは違う。TDDもBDDも同じ言語で仕様を手続き的に書ける点、個々を完全に定義するよりも、徐々に網羅していくようにチェックを作れる、という特徴がある。仕様記述言語は長年続いてなお普及してるとはいいにくいが、TDDは簡単に普及した。この差は重要である。