エグザンプル
RSpecは「ビヘイビア駆動開発(Behavior Driven Development:BDD)」というプログラム開発手法をRubyで実践するために作られたテストフレームワークです。従来の開発手法との違いを強調するためか、RSpecは独特の用語を採用しています。その1つがエグザンプル(example)です。
さきほど作成したstring_spec.rbでexampleとendで囲まれた部分がありました。
4 | expect(s.size).to eq(4) |
これがエグザンプルです。exampleメソッドの引数には、このエグザンプルを説明する短い文を指定します。
exampleメソッドには、itという別名があります。これを利用すると、先ほどの例は次のように書けます。
1 | it 'appends a character' do |
4 | expect(s.size).to eq(4) |
エグザンプルの説明を英語で記述する場合、itと引数で1つの文のように見えるため、この別名がしばしば使われます。exampleメソッドには、さらにspecifyという別名もあります。
もちろん日本語で説明を記述する場合でもitを使用して構いませんが、私は違和感があるため使いません。
さて、エグザンプル(example)を日本語に直訳すれば「例」あるいは「用例」です。この用語は、伝統的な用語で言えばテストケース(test case)に相当します。すなわち、ソフトウェアのある機能の前提条件と期待される結果をコードで表現したものです。
なぜRSpecはそれをエグザンプルと呼ぶのでしょうか。それは、ビヘイビア駆動開発では、テストコードによってソフトウェアの仕様(specification)が定義される、と考えるからです。RSpecという名前もそこに由来しています。RSpecは単にソフトウェアのテストをするだけでなく、ソフトウェアの仕様を記述することに重点を置いています。ある意味では、テストコードが仕様書の代わりになるのです。仕様は言葉で表現するよりも、実際の用例を列挙した方がわかりやすいものです。
エグザンプルグループ
RSpecではいくつかの関連するエグザンプルをエグザンプルグループ(example group)としてまとめることができます。先ほどのstring_spec.rbをご覧ください。
describeとendで囲まれた部分がエグザンプルグループです。describeメソッドの引数には、クラスまたは文字列を指定します。エグザンプルグループは入れ子構造にすることが可能です。ここでは、全体としてStringクラスに関する仕様をまとめたエグザンプルグループを作った後、その内側に
テスト結果の読み方
当然のことながら、テストは失敗することもあります。テストが失敗するとどんな結果になるのか見るため、string_spec.rbに意図的に失敗するエグザンプルを追加してみましょう。
spec/experiments/string_spec.rb
08 | expect(s.size).to eq(4) |
14 | expect(s.size).to eq(4) |
実行してみましょう。
01 | $ bin/rspec spec/experiments/string_spec.rb |
07 | Failure/Error: s << nil |
09 | no implicit conversion of nil into String |
10 | # ./spec/experiments/string_spec.rb:13:in `block (3 levels) in <top>' |
13 | Finished in 0.09223 seconds |
18 | rspec ./spec/experiments/string_spec.rb:11 # String#<< nilの追加 |
結果の1行目にはドット(.)とFという文字が出力されています。複数のエグザンプルを実行する場合、その実行順はランダムなので、もしかするとFが先に表示されているかもしれません。これらの文字の数はエグザンプルの数と同じです。ドットは成功、Fは失敗を意味します。
Failures:以下には、失敗したエグザンプルについて説明が表示されています。「String#
2 | no implicit conversion of nil into String |
ここから例外TypeErrorが発生したことがわかります。「nilは暗黙裏にStringに変換されない」と説明が書かれています。その下のシャープ記号(#)で始まる3行は、エラーのバックトレース(呼び出し履歴)です。
pending
テストが失敗したら、通常はテスト対象のクラスかspecファイルのいずれかに誤りがあるということですので、すべてのテストが成功するまでソースコードの修正とbin/rspecコマンドの実行を繰り返すことになります。しかし、原因がわからないとか時間が足りないといった理由ですぐには直せないこともあります。その場合は、次のようにpendingメソッドを使ってエグザンプルに「未解決(pending)」の印を付けます。
spec/experiments/string_spec.rb
15 | expect(s.size).to eq(4) |
pendingメソッドの引数には、未解決である理由などを表す文字列を指定してください。この状態でテストを実行すると結果は次のようになります。
06 | # ./spec/experiments/string_spec.rb:12 |
08 | Finished in 0.12296 seconds |
10 | 2 examples, 0 failures, 1 pending |
1行目のアスタリスク(*)は未解決の状態にあるエグザンプルを表します。5行目にpendingメソッドに与えた文字列が表示されています。
pendingメソッドを書くのが面倒な場合には、exampleをxexampleに書き換えるという方法もあります。
spec/experiments/string_spec.rb
10 | expect(s.size).to eq(4) |
12 | expect(s.size).to eq(4) |
この場合は、テストの実行結果は次のようになります。
5 | # Temporarily disabled with xexample |
6 | # ./spec/experiments/string_spec.rb:12 |
8 | Finished in 0.12296 seconds |
9 | 2 examples, 0 failures, 1 pending |
4行目に「Temporarily disabled with xexample」と出力されています。「xexampleにより一時的に無効化されている」という意味です。
expectメソッドとマッチャー
●オブジェクトを対象にする場合
さて、ここまで後回しにしてきたexpectメソッドについて説明しましょう。expectメソッドの一般的な使用法は、以下のとおりです。
Tをターゲット、Mをマッチャーと呼びます。string_spec.rbでは次のように書かれていましたね。
ターゲットはs.sizeメソッドが返すオブジェクト、マッチャーはeq(4)が返すオブジェクトです。
マッチャーは、ターゲットに指定されたオブジェクトがある条件を満たすかどうか(マッチするかどうか)を調べるオブジェクトです。ターゲットがその条件を満たさなければ、そのエグザンプルは失敗したと判定されます。
eqメソッドは引数に指定したオブジェクトとターゲットが等しいかどうかを調べるマッチャーを返します。string_spec.rbの場合は、s.size == 4であれば成功、そうでなければ失敗です。
expectメソッドの後ろに付いているtoをnot_toに変えると、全体の意味が反転します。
1 | expect(s.size).not_to eq(4) |
s.sizeが4に等しくなければ成功で、等しければ失敗になります。
RSpecには数多くのマッチャーが用意されています。本書を通じて少しずつ紹介していきます。
●ブロックを対象にする場合
expectメソッドにはexpect(T).to Mの他にもう1つの使用法があります。
{ ... }はブロックです。実例をお見せしましょう。string_spec.rbを次のように修正してください。
spec/experiments/string_spec.rb
11 | example 'nilは追加できない' do |
13 | expect { s << nil }.to raise_error(TypeError) |
raise_errorマッチャーは、expectメソッドに指定されたブロックが特定の例外を発生させることを確かめます。ここではs
エグザンプルの絞り込み
●行番号による絞り込み
すでに見たようにbin/rspecコマンドにspecファイルのパスを指定すれば、そのspecファイルに記述されたエグザンプルだけを実行できます。しかし、特定のspecファイルの中の特定のエグザンプルだけを実行したい場合もあります。そのときは、次のように--line-numberオプション(または-lオプション)で行番号を指定してください。
1 | $ bin/rspec spec/experiments/string_spec.rb --line-number 11 |
2 | $ bin/rspec spec/experiments/string_spec.rb -l 11 |
この場合は、11行目で定義されているエグザンプルだけが実行されます。
あるいは、次のようにパスの後ろにコロン(:)と行番号を指定しても同じ結果となります。
1 | $ bin/rspec spec/experiments/string_spec.rb:11 |
●タグによる絞り込み
次にタグを使ってエグザンプルを絞り込む方法を紹介します。string_spec.rbを次のように修正してください。
spec/experiments/string_spec.rb
11 | example 'nilは追加できない', :exception do |
13 | expect { s << nil }.to raise_error(TypeError) |
2つ目のexampleメソッドの第2引数に加えた:exceptionというシンボルがタグです。こうしておくと、次のコマンドで:exceptionタグの付いたエグザンプルだけをまとめて実行できます。
1 | $ bin/rspec spec --tag=exception |