RailsのテストフレームワークRSpecの基礎知識

2014年7月8日(火)
黒田 努
実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング
顧客管理システムの構築を体験しながら、Railsアプリケーション開発のノウハウを習得!Amazon詳細ページへ
この記事は、書籍『実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング』の内容を、Think IT向けに特別にオンラインで公開しているものです。詳しくは記事末尾の書籍紹介欄をご覧ください。

エグザンプル

RSpecは「ビヘイビア駆動開発(Behavior Driven Development:BDD)」というプログラム開発手法をRubyで実践するために作られたテストフレームワークです。従来の開発手法との違いを強調するためか、RSpecは独特の用語を採用しています。その1つがエグザンプル(example)です。

さきほど作成したstring_spec.rbでexampleとendで囲まれた部分がありました。

1example '文字の追加' do
2  s = "ABC"
3  s << "D"
4  expect(s.size).to eq(4)
5end

これがエグザンプルです。exampleメソッドの引数には、このエグザンプルを説明する短い文を指定します。

exampleメソッドには、itという別名があります。これを利用すると、先ほどの例は次のように書けます。

1it 'appends a character' do
2  s = "ABC"
3  s << "D"
4  expect(s.size).to eq(4)
5end

エグザンプルの説明を英語で記述する場合、itと引数で1つの文のように見えるため、この別名がしばしば使われます。exampleメソッドには、さらにspecifyという別名もあります。

もちろん日本語で説明を記述する場合でもitを使用して構いませんが、私は違和感があるため使いません。

さて、エグザンプル(example)を日本語に直訳すれば「例」あるいは「用例」です。この用語は、伝統的な用語で言えばテストケース(test case)に相当します。すなわち、ソフトウェアのある機能の前提条件と期待される結果をコードで表現したものです。

なぜRSpecはそれをエグザンプルと呼ぶのでしょうか。それは、ビヘイビア駆動開発では、テストコードによってソフトウェアの仕様(specification)が定義される、と考えるからです。RSpecという名前もそこに由来しています。RSpecは単にソフトウェアのテストをするだけでなく、ソフトウェアの仕様を記述することに重点を置いています。ある意味では、テストコードが仕様書の代わりになるのです。仕様は言葉で表現するよりも、実際の用例を列挙した方がわかりやすいものです。

エグザンプルグループ

RSpecではいくつかの関連するエグザンプルをエグザンプルグループ(example group)としてまとめることができます。先ほどのstring_spec.rbをご覧ください。

1require 'spec_helper'
2 
3describe String do
4  describe '#<<' do
5    example '文字の追加' do
6      (省略)
7    end
8  end
9end

describeとendで囲まれた部分がエグザンプルグループです。describeメソッドの引数には、クラスまたは文字列を指定します。エグザンプルグループは入れ子構造にすることが可能です。ここでは、全体としてStringクラスに関する仕様をまとめたエグザンプルグループを作った後、その内側に

テスト結果の読み方

当然のことながら、テストは失敗することもあります。テストが失敗するとどんな結果になるのか見るため、string_spec.rbに意図的に失敗するエグザンプルを追加してみましょう。

spec/experiments/string_spec.rb

01require 'spec_helper'
02 
03describe String do
04  describe '#<<' do
05    example '文字の追加' do
06      s = "ABC"
07      s << "D"
08      expect(s.size).to eq(4)
09    end
10 
11    example 'nilの追加' do
12      s = "ABC"
13      s << nil
14      expect(s.size).to eq(4)
15    end
16  end
17end

実行してみましょう。

01$ bin/rspec spec/experiments/string_spec.rb
02.F
03 
04Failures:
05 
06  1) String#<< nilの追加
07     Failure/Error: s << nil
08     TypeError:
09       no implicit conversion of nil into String
10     # ./spec/experiments/string_spec.rb:13:in `block (3 levels) in <top>'
11     # -e:1:in `<main>'
12 
13Finished in 0.09223 seconds
142 examples, 1 failure
15 
16Failed examples:
17 
18rspec ./spec/experiments/string_spec.rb:11 # String#<< nilの追加
19</main></top>

結果の1行目にはドット(.)とFという文字が出力されています。複数のエグザンプルを実行する場合、その実行順はランダムなので、もしかするとFが先に表示されているかもしれません。これらの文字の数はエグザンプルの数と同じです。ドットは成功、Fは失敗を意味します。

Failures:以下には、失敗したエグザンプルについて説明が表示されています。「String#

1TypeError:
2  no implicit conversion of nil into String

ここから例外TypeErrorが発生したことがわかります。「nilは暗黙裏にStringに変換されない」と説明が書かれています。その下のシャープ記号(#)で始まる3行は、エラーのバックトレース(呼び出し履歴)です。

pending

テストが失敗したら、通常はテスト対象のクラスかspecファイルのいずれかに誤りがあるということですので、すべてのテストが成功するまでソースコードの修正とbin/rspecコマンドの実行を繰り返すことになります。しかし、原因がわからないとか時間が足りないといった理由ですぐには直せないこともあります。その場合は、次のようにpendingメソッドを使ってエグザンプルに「未解決(pending)」の印を付けます。

spec/experiments/string_spec.rb

09    (省略)
10 
11    example 'nilの追加' do
12      pending('調査中')
13      s = "ABC"
14      s << nil
15      expect(s.size).to eq(4)
16    end
17  end
18end

pendingメソッドの引数には、未解決である理由などを表す文字列を指定してください。この状態でテストを実行すると結果は次のようになります。

01*.
02 
03Pending:
04  String#<< nilの追加
05    # 調査中
06    # ./spec/experiments/string_spec.rb:12
07 
08Finished in 0.12296 seconds
09 
102 examples, 0 failures, 1 pending

1行目のアスタリスク(*)は未解決の状態にあるエグザンプルを表します。5行目にpendingメソッドに与えた文字列が表示されています。

pendingメソッドを書くのが面倒な場合には、exampleをxexampleに書き換えるという方法もあります。

spec/experiments/string_spec.rb

01require 'spec_helper'
02 
03describe String do
04  describe '#<<' do
05    (省略)
06 
07    xexample 'nilの追加' do
08      s = "ABC"
09      s << nil
10      expect(s.size).to eq(4)
11    end
12  end
13end
09    xexample 'nilの追加' do
10      s = "ABC"
11      s << nil
12      expect(s.size).to eq(4)
13    end
14  end
15end

この場合は、テストの実行結果は次のようになります。

1*.
2 
3Pending:
4  String#<< nilの追加
5    # Temporarily disabled with xexample
6    # ./spec/experiments/string_spec.rb:12
7 
8Finished in 0.12296 seconds
92 examples, 0 failures, 1 pending

4行目に「Temporarily disabled with xexample」と出力されています。「xexampleにより一時的に無効化されている」という意味です。

expectメソッドとマッチャー

●オブジェクトを対象にする場合

さて、ここまで後回しにしてきたexpectメソッドについて説明しましょう。expectメソッドの一般的な使用法は、以下のとおりです。

1expect(T).to M

Tをターゲット、Mをマッチャーと呼びます。string_spec.rbでは次のように書かれていましたね。

1expect(s.size).to eq(4)

ターゲットはs.sizeメソッドが返すオブジェクト、マッチャーはeq(4)が返すオブジェクトです。

マッチャーは、ターゲットに指定されたオブジェクトがある条件を満たすかどうか(マッチするかどうか)を調べるオブジェクトです。ターゲットがその条件を満たさなければ、そのエグザンプルは失敗したと判定されます。

eqメソッドは引数に指定したオブジェクトとターゲットが等しいかどうかを調べるマッチャーを返します。string_spec.rbの場合は、s.size == 4であれば成功、そうでなければ失敗です。

expectメソッドの後ろに付いているtoをnot_toに変えると、全体の意味が反転します。

1expect(s.size).not_to eq(4)

s.sizeが4に等しくなければ成功で、等しければ失敗になります。

RSpecには数多くのマッチャーが用意されています。本書を通じて少しずつ紹介していきます。

●ブロックを対象にする場合

expectメソッドにはexpect(T).to Mの他にもう1つの使用法があります。

1expect { ... }.to M

{ ... }はブロックです。実例をお見せしましょう。string_spec.rbを次のように修正してください。

spec/experiments/string_spec.rb

09    (省略)
10 
11    example 'nilは追加できない' do
12      s = "ABC"
13      expect { s << nil }.to raise_error(TypeError)
14    end
15  end
16end

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

09    (省略)
10 
11    example 'nilは追加できない', :exception do
12      s = 'ABC'
13      expect { s << nil }.to raise_error(TypeError)
14    end
15  end
16end

2つ目のexampleメソッドの第2引数に加えた:exceptionというシンボルがタグです。こうしておくと、次のコマンドで:exceptionタグの付いたエグザンプルだけをまとめて実行できます。

1$ bin/rspec spec --tag=exception
株式会社オイアクス

東京大学教養学部卒。同大学院総合文化研究科博士課程満期退学。ギリシャ近現代史専攻。専門調査員として、在ギリシャ日本国大使館に3年間勤務。中学生の頃に出会ったコンピュータの誘惑に負け、IT業界に転身。株式会社ザッパラス技術部長、株式会社イオレ取締役を経て、技術コンサルティングとIT教育を事業の主軸とする株式会社オイアクスを設立。現在、同社代表取締役社長。また、2011年末にRuby on Rails によるウェブサービス開発専業の株式会社ルビキタスを知人と共同で設立し同社代表に就任(オイアクス社長と兼任)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています