RSpec初心者向け 手続き化されたRSpecのテストコードの書き方

こちらの記事が非常に参考になったので、個人的備忘録として手順をまとめて書きます。
そうやることの意味などの説明は削げ落ちていますので引用元を参照してください。
また、おそらくRSpecに慣れた人にとっては無駄なコードを書いているとしか思えないかもしれませんが、そこは初心者向けということで一つよろしくお願いします。

まず、この手法は3つのステップからなります。

I. 冗長になることを気にせず、形式に沿って型通りのテストコードのテンプレを実装せずに書く

  1. トップレベルに「describe 'クラス名(ex: NumberStack)'」を書く
  2. そのクラスの取りうる状態を「context '状態名(ex: when stack is full)'」の形式で書く*1
  3. さらにそのそれぞれのcontextの中にテスト対象のメソッドすべてを「describe 'メソッド名(ex: NumberStack#push)'」の形式で列挙する*2
  4. 引数によって振る舞いが変わるメソッドの場合、さらにそのメソッドのdescribeの中で「context '状態名(ex: with non-Number)'」で書く
  5. 各メソッドが返すべき出力を「it '返すべき値(ex: raise error)'」の形式で書きます。この際do-endは書く必要がないので*3注意してください。

II. I.で書いたテンプレートの形を崩さずにテストを書く

非常に助長になりますが、テンプレートの形を崩さないように気をつけてテストコードを書きます。
最初にいちばん外側のdescribe内で「subject { "クラス名".new }」として、テスト内からインスタンスを利用するときは新たにnewするのではなく、「subject.empty?」などとアクセスしてください。
また、テスト本体を書く前にクラスの各context内でbeforeを使って初期化するのを忘れないでください。

III. 書いたコードの冗長な部分をどんどん省略していく

どんどんRSpecの機能を用いて省略していきますが、基準としてはリンク先にも書いてある通り「% rspec -fd ~」で実行した時、英語として読めるかどうかです。
何をテストしているのかわからないようでは意味がありません。
非常に多くのテクニックがあります。

be_XXX系マッチャーの使用 NumberStack#empty?のようなtrue/falseを返すメソッドは、be_emptyなどと書くことができます
subjectの省略 テストコード内でshouldから書き始めた場合、そのレシーバはすべてsubjectになるため、省略できるところは削除します
itsの使用 テスト対象のメソッドをits(:メソッド名)の形式で書きます。コンマに続けて引数も渡せます
itsの使用時はそのすぐ外のcontextまたはdescribeを削除 itsを使用した場合「% rspec -fd ~」で説明が二重に出てしまうので、describeでの記述を削除します
説明が重複しているところのdescribe(context)の削除 上と関連した話ですが、「% rspec -fd ~」したときに同じ意図のテキストが表示される場合はそのdescribe(またはcontext)を削除します
1行かつ短いテストコードの場合do-endをやめて{}を使用 3行が1行になります

この他にもshared example、custom matcherなどと行った機能があるらしいです。

蛇足

書きませんでしたが、このテストの対する実装はこれらの手続き全てが終わった後ではなく、II.とIII.の間に行います。III.のリファクタリングはすべてのテストがグリーンになったのを確認してから行うというわけです。
これは不用意なリファクタリングで間違ったテストコードを書かないようにという意味だと思います。

*1:状態の列挙には同値分割や境界値分析を意識して行う

*2:この時点でクラスの状態数×メソッド数の数だけdescribeが書かれると思います

*3:書かなければrspecコマンドで自動的にテストが見実装と表示されます