RSpecとは
▶ Ruby on Railsで用いられる、テストフレームワークです。RSpecをうまく活用するることで、簡潔で読みやすいテストコードを書くことができ、Railsアプリケーションの保守性を高めることができます。
▶ ちなみにRuby on RailsにはMinitestというテストフレームワークが標準で組み込まれているのですが、私はまだ学習していないので、今回はRSpecについて投稿したいと思います。
1. RSpecの初期設定
GemファイルにRSpecのGemを追記
# Gemfile
group :development, :test do # 開発・テストのグループに記述してGem動作に制限をもたせる
# 省略
gem 'rspec-rails', '~> 4.0.0' # ← gemの追記
end
ターミナルでbundle installを実行
アプリケーションのディレクトリに移動して以下のコマンドを実行
% bundle install
引き続きRSpecをインストール
% rails g rspec:install
インストール完了後、以下のディレクトリやファイルが生成されます。
ターミナルのログ
Running via Spring
preloader in process 1087
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
また生成された.rspec.rbに以下を追記することでターミナルでテストコードの結果を可視化する事ができます。
# spec/.rspec.rb
--format documentation
以上で初期設定はおしまいです。次は実際にテストコードを書いていきたいと思います。
2. RSpecによる簡単なテストコードを書く
それではテストコードを書いていきたいと思います。
specファイルはspecディレクトリのサブディレクトリに分類して配置します。どのサブディレクトリにどんなspecファイルを置くかは慣習的に決まっています。たとえば、モデルクラスに関するspecファイルはspec/modelsディレクトリに、APIに関するspecファイルspec/requestsディレクトリに置くのが一般的です。
今回は説明のためにサブディレクトリとしてtestsディレクトリを作成し説明していきます。
specディレクトリにtestsディレクトリのサブディレクトリを作り、その直下にstring_spec.rbのファイルを作ります。この時、ファイル名は特に気にしなくて良いのですが、ファイルの末尾は_spec.rbとする必要があります。
文字の追加が正しく行われているかテスト
# spec/tests/string_spec.rb
require "spec_helper"
describe String do
it "文字の追加" do
str = "あいう" # 変数strを定義
str << "え" # strに"え"を追加
expect{str.size}.to eq(4) # expectメソッドで変数strの状態を調べる。文字の追加後、文字数は4文字("あいうえ")
end
end
describeメソッド
テストコードのグループ分けを行うメソッドです。「どの機能に対してのテストを行うか」をdescribeでグループ分けし、その中に各テストコードを記述します。また、describeとendで囲まれた部分をエグザンプルグループといいます。describeメソッドの引数には、クラスまたは文字列を指定します。エグザンプルグループは入れ子構造にすることが可能です。
itメソッド
describeメソッド同様に、グループ分けを行うメソッドです。itの場合はより詳細に、「describeメソッドに記述した機能において、どのような状況のテストを行うか」を明記します。
またexampleという用法もあり、itで分けたグループのことを指します。また、itに記述した内容のことを指す場合もあります。
expectation(エクスペクテーション)
エクスペクテーションとは、検証で得られた挙動が想定通りなのかを確認する構文です。
expect(T).to M を雛形に、テストの内容に応じてT(引数)やM(マッチャ)を変えて記述します。
matcher(マッチャ)
matcherは、「expectの引数」と「想定した挙動」が一致しているかどうかを判断します。
expectの引数には検証で得られた実際の挙動を指定し、マッチャには、どのような挙動を想定しているかを記述します。
今回はeqとういマッチャを使用しています。eqは、「expectの引数」と「eqの引数」が等しいことを確認するマッチャです。
それでは、ターミナルでテストコードを実行します。
ターミナル
% bundle exec rspec spec/tests/string_spec.rb
ログは以下のようになります。テストは成功です。
Finished in 0.01423 seconds (files took 0.15324 seconds to load)
1 example, 0 failures
失敗する場合
次に失敗の場合を見てみます。追加文字を"えお"の2文字にします。
# spec/tests/string_spec.rb
require "spec_helper"
describe String do
it "文字の追加" do
str = "あいう" # 変数strを定義
str << "えお" # strに"えお"を追加
expect{str.size}.to eq(4) # expectメソッドで変数strの状態を調べる。文字の追加後、文字数は5文字("あいうえお")
end
end
ログは以下のようになります。テストは失敗です。
String
文字の追加 (FAILED - 1)
Failures:
1) String 文字の追加
Failure/Error: expect(s.size).to eq(4)
expected: 4
got: 5
(compared using ==)
# ./spec/tests/string_spec.rb:7:in `block (2 levels) in <top (required)>'
Finished in 0.03041 seconds (files took 0.13182 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/tests/string_spec.rb:4 # String 文字の追加
この時、 「Failure/Error: expect(s.size).to eq(4)」の後に「expected: 4 got: 5」
という記述があり、想定は4文字なのに実際は5文字であるというエラーメッセージがあります。このようにテストが失敗した場合、ログに失敗の原因のヒントがあります。
3. 保留中(pendingメソッド)
基本的には、すべてのテストが成功するまでソースコードの修正とテストの実行を繰り返すことになります。しかし、原因が分からないとか時間が足りないといった理由ですぐには直せない場合もあります。その場合は、pendingメソッドを使ってエグザンプルに「保留中(pending)」の印を付けることができます。
require "spec_helper"
describe String do
it "文字の追加" do
pending("原因調査中") # pendingメソッドには理由を示す引数を与える。この場合"原因調査中"
s = "あいう"
s << "えお"
expect(s.size).to eq(4)
end
end
この状態でテストを実行すると次のようになります。
String
文字の追加 (PENDING: 原因調査中)
Pending: (Failures listed here are expected and do not affect your suite's status)
1) String 文字の追加
# 原因調査中
Failure/Error: expect(s.size).to eq(4)
expected: 4
got: 5
(compared using ==)
# ./spec/tests/string_spec.rb:8:in `block (2 levels) in <top (required)>'
Finished in 0.0292 seconds (files took 0.13358 seconds to load)
1 example, 0 failures, 1 pending
また、pendingメソッドの代わりにxexamplメソッドがあります。今回exampleを使用していないのですが、単純に対象のexampleをxexampleに書き換えるだけです。
これを使うと、ターミナルのログに「xexampleにより一時的に無効化されている」という意味である「Temporarily disabled with xexample」が出力されます。
4. エグザンプルの絞り込み
複数のエグザンプルグループを持つテストコードの例
# spec/tests/test_spec.rb
require "spec_helper"
describe String do # 行番号3
it "文字の追加" do
s = "あいう"
s << "え"
expect(s.size).to eq(4)
end
end
describe Integer do # 行番号11
it "数字の足し算" do
i = 1
i += 1
expect(i).to eq(2)
end
end
複数のエグザンプルグループを持つテストコードを実行させる場合、処理に時間がかかることもあり、特定のエグザンプルグルームのみをテストしたい場合があります。
行番号による絞り込み
次のようにパスの後ろにコロン(:)と行番号を指定する。今回は行番号11のエグザンプルグループのみを実行します。
ターミナル
% bundle exec rspec spec/tests/test_spec.rb:11
ターミナルのログ
Run options: include {:locations=>{"./spec/tests/string_spec.rb"=>[11]}}
Integer
数字の足し算
Finished in 0.0039 seconds (files took 0.13787 seconds to load)
1 example, 0 failures
タグによる絞り込み
test_spec.rbを次のように修正します。
# spec/tests/test_spec.rb
require "spec_helper"
# 途中省略 #
describe Integer do # 行番号11
it "数字の足し算" , :exception do # [, :exception]の追記
i = 1
i += 1
expect(i).to eq(2)
end
end
exampleメソッドの第2引数に加えた:exceptionというシンボルがタグです。こうしておくと、次のコマンドで:exceptionタグの付いたエグザンプルだけをまとめて実行できます。
ターミナル
% bundle exec rspec spec/tests/test_spec.rb --tag=exception
ターミナルのログ
Run options: include {:exception=>true}
Integer
数字の足し算
Finished in 0.004 seconds (files took 0.15853 seconds to load)
1 example, 0 failures
2つあるエグザンプルグループのうち片方のみを実行できました。今回は2つだけですが、実際にアプリケーション開発で多数のエグザンプルグループに分けてテストコードを書くことがあります。1つのエグザンプルグループのコードを修正して、再度テストするときに、他のエグザンプルグループをテストすると非効率です。そのためエグザンプルの絞り込みは重要だと思います。
最後に
今回はRSpecの基礎について投稿させていただきました。紹介したコードは実際に実行して、間違いがないか試していますが、もし誤り等あればご指摘のほうよろしくお願いします。