Railsのテスト

このガイドでは、アプリケーションのためのRails組み込みテストについて説明します。 このガイドを読むことで、次の事が学べるはずです。

  • Railsでのテストの専門用語
  • アプリケーションのユニット、機能、統合テストの書き方について
  • 他の一般的なテスト手法やプラグイン

1. 何故テストを書くのか?

Railsは、テストを書くことを非常に簡単にしてくれます。 モデルとコントローラー作成の際に生成されたスケルトンを使って始めてみましょう。

単にRailsテストを実行することによって、主要なコードのリファクタリングの後でも、 コードが望むような動きをすることを保証することが出来ます。

Railsはブラウザのリクエストをシミュレートすることも可能であるため、 アプリケーションのレスポンスのテストをブラウザを使用すること無く行うことが可能です。

2. テストの導入について

テストをサポートする機能は、最初からRailsに組み込まれています。 "流行っていてクールだから取り入れよう!"という理由で組み込まれたわけではありません。 ほぼ全てのRailsアプリケーションには、データベースとのやり取りが頻繁にあるため、 同様にテストでもデータベースとのやり取りが必要になります。 効率的なテストを書くために、このデータベースの設定とデータを問入れる方法について理解する必要があるでしょう。

2.1 テスト環境

デフォルトでRailsアプリケーションは、developmenttestproductionの3つの環境を持ちます。 それらの各データベースの環境は、config/database.ymlによって設定されます。

専用のテスト用データベースを使用することで、分離したテストデータの設定とそのデータの取り扱いが可能になります。 テストする際に、developmentまたはproductionのデータに触れることが無いため、 データを好きなように扱うことが出来ます。

2.2 Railsによるテストのセットアップ

Railsはrails new application_nameを使用してプロジェクトを作成すると、 testフォルダを作成します。 このフォルダ内の一覧を確認すると、次のような一覧が確認出来るはずです。

$ ls -F test
controllers/    helpers/        mailers/        test_helper.rb
fixtures/       integration/    models/

modelsディレクトリはモデルのテストを、 controllersディレクトリはコントローラーのテストを、 integrationディレクトリは幾つかのコントローラーのやり取りを含むテストを、それぞれ保持することを意味します。

Fixturesは、fixturesフォルダ内のファイルを使用して、テストデータを構成するための手段です。

test_helper.rbファイルは、テストのためのデフォルト構成を保持しています。

2.3 Fixturesについて

良いテスト行うために、テスト用のデータを設定して渡す必要があります。 Railsでは、定義とfixturesのカスタマイズによって、これを取り扱うことが出来ます。

2.3.1 Fixturesとは何か?

fixturesとはサンプルデータを飾った単語です。 fixturesは、テストを実行する前に予めデータをテスト用のデータベースに取り入れることを可能にしてくれます。 fixturesはYAMLに依存するデータで、モデル毎に1つのファイルが存在します。

fixturesは、test/fixturesディレクトリ配下に格納されています。 rails generate modelで新しいモデルを作成する際に、 fixtureのstub(サンプル、控え)が自動的に作成され、このディレクトリに置かれます。

2.3.2 YAML

YAMLフォーマットのfixtureは、人間にとってとても分かり易い方法でサンプルデータを書けるようになっています。 これらのタイプのfixtureファイルは、.yml拡張子を持ちます。(例えば、users.yml)

下記は、YAMLのfixtureファイルのサンプルになります。

# lo & behold!  I am a YAML comment!
david:
  name: David Heinemeier Hansson
  birthday: 1979-10-15
  profession: Systems development

steve:
  name: Steve Ross Kellock
  birthday: 1974-09-27
  profession: guy with keyboard

各fixtureは、名前とそれに続くインデントされたコロン区切りのキー/値のペアのリストが与えられます。 各レコードは通常、空白によって区切られます。 先頭に#文字を置くことで、コメントを書くことが可能です。 'yes'と'no'のようなYAMLキーワードに似たキーはクォートされると、YAMLパーサーはこれを正しく解釈します。

もしモデル間の関連付けをしていても、2つの異なるfixture間で参照するように定義するだけです。 下記は、belongs_tohas_manyの関連付けの場合の例です。

# In fixtures/categories.yml
about:
  name: About

# In fixtures/articles.yml
one:
  title: Welcome to Rails!
  body: Hello world!
  category: about
2.3.3 ERBの埋め込み

ERBは、テンプレート内にRubyコードを埋め込めるようにしてくれる仕組みです。 Railsはfixtureを読み込む際に、YAMLのfixtureフォーマットのERBが事前に処理されます。 これはRubyを使用したサンプルデータの作成の手助けをしてくれます。 例えば、下記のコードは1000ユーザーを生成します。

<% 1000.times do |n| %>
user_<%= n %>:
  username: <%= "user#{n}" %>
  email: <%= "user#{n}@example.com" %>
<% end %>
2.3.4 fixtureのアクション

モデルとコントローラーのテストのために、Railsはデフォルトでtest/fixturesフォルダから全てのfixtureを自動的に読み込みます。 読み込みは次の3つの手順で行われます。

  • 既存のデータをfixtureに対応するテーブルから削除します。
  • fixtureのデータをテーブルに読み込みます。
  • それに直接アクセスしたい場合は(?)、変数にfixtureデータを吐き出します。
2.3.5 Activeレコードのオブジェクトとしてのfixture

fixtureは、Activeレコードのインスタンスです。 上記の#3で言及したように、テストの場合ローカル変数として自動的に設定されるため、 オブジェクトに直接アクセスすることが出来ます。 例えば、

# fixtureのためにdavidと名付けられたUserオブジェクトを返します。
users(:david)

# davidのidを返します。
users(:david).id

# Userクラス上で利用可能なメソッドにもアクセス可能です。
email(david.girlfriend.email, david.location_tonight)

3. モデルのユニットテスト

Railsでは、モデルのテストは、モデルをテストするために書いたものになります。

このガイドのために、Railsのスキャフォールドを使用します。 この操作を行うことで、モデル、マイグレーション、コントローラー、ビューを新しいリソースのために作成します。 また、スキャフォールドは、Railsのベストプラクティスに従った一通りのテスト一式も作成します。 この生成されたコードを例に使用して、必要に応じて補足と追加をしていきます。

Railsのスキャフォールドについての詳しい情報を知りたければ、ガイドのRails入門を参照してください。

rails generate scaffoldを使用すると、 test/modelsフォルダ内にテストのstub(サンプル、控え)が作成されます。

$ rails generate scaffold post title:string body:text
...
create  app/models/post.rb
create  test/models/post_test.rb
create  test/fixtures/posts.yml
...

test/models/post_test.rb内のデフォルトのテストのstubは、 下記のようになります。

require 'test_helper'

class PostTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

このファイルの1行目の検証によって、Railsをテストコードと用語に適応させるための手助けをしてくれます。

require 'test_helper'

既にご存知のとおり、test_helper.rbは自分のテスト実行のためのデフォルト設定を指定します。 全てのテストにこのファイルが含まれると、それに追加されているメソッドが全てのテストで利用可能になります。

class PostTest < ActiveSupport::TestCase

ActiveSupport::TestCaseを継承するため、PostTestクラスはテストケースを定義します。 そのため、PostTestはActiveSupport::TestCaseで利用可能なメソッドを使用することが出来ます。 この後このガイドで、これらのメソッドを確認することになるでしょう。

testから始まるMiniTest::Unit::TestCase(ActiveSupport::TestCaseのスーパークラス)から継承されクラスに定義されたメソッドは、 単純にテストと呼ばれています。 そのため、test_passwordtest_valid_passwordtestValidPasswordの全ては、 定められたテスト名であり、テストケース実行時に自動的に実行されます。

Railsはテスト名とブロックを取得するtestメソッドを追加します。 これはメソッド名の接頭辞にtest_が付く通常のMiniTest::Unitテストを生成します。 そのため、

test "the truth" do
  assert true
end

上記は、下記のように書いたものとして振る舞います。

def test_the_truth
  assert true
end

テストマクロのみの方が、よりテスト名が読みやすいものになりますが、 通常のメソッド定義を使用することも可能です。

メソッド名は、アンダースコアを空白に置き換えて生成されます。 その結果が、有効なRuby識別子である必要はありませんが、 その名前には句読点などが含まれているかもしれません。 Rubyでは、専門的な(技術的な?)な文字列が、メソッド名になることがあるからです。 これらの変わったものは、define_methodsendの呼び出しが必要ですが、形式上の制約はありません。

assert true

この行のコードは、アサーションと呼ばれています。 アサーションとは、オブジェクト(または式)が期待する結果になるか検証するコード行です。 例えば、アサーションは下記の項目をチェックすることが出来ます。

  • 値は期待する値になっているか?
  • オブジェクトはnilであるか?
  • このコードは例外をスローするか?
  • ユーザーのパスワードは5文字以上であるか?

各テストは、1つ以上のアサーションを含みます。 全てのアサーションが成功した場合ににみ、テストにパスします。

3.1 アプリケーションをテストするための準備

テストを実行する前に、データベースの構造が最新のものであるか確認する必要があります。 これは下記のrakeコマンドを使用して確認することが出来ます。

$ rake db:migrate
...
$ rake db:test:load

上記のdb:migrateは、development環境で保留になっているマイグレーションを実行し、 db/schema.rbを更新します。 db:test:loadは、最新のdb/schema.rbからtestデータベースを再構築します。 始めにdb:test:prepareを実行して、保留中のマイグレーションが無いか確認することをお勧めします。

db:test:prepareは、db/schema.rbが存在しないとエラーとなり失敗します。

3.1.1 テスト準備のためのRakeタスク
タスク 説明
rake db:test:clone

現在の環境のデータベーススキーマから、testデータベースを再構築します。

rake db:test:clone_structure

developmentの構造から、testデータベースを再作成します。

rake db:test:load

現在のschema.rbから、testデータベースを再作成します。

rake db:test:prepare

保留になっているマイグレーションを確認し、テストスキーマを読み込みます。

rake db:test:purge

testデータベースを空にします。

rake --tasks --describeを実行すると、 これら全てのrakeタスクとその説明を見ることが出来ます。

3.2 テストの実行

テストの実行は、単にテストケースを含むファイルをrake testコマンドを通して実行するだけです。

$ rake test test/models/post_test.rb
.

Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

テストケースから、テストのメソッド名を提供して実行することで、独自のテストメソッドを実行することも可能です。

$ rake test test/models/post_test.rb test_the_truth
.

Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

テストケースから、全てのテストメソッドを実行します。 test_helper.rbtestディレクトリ内にあるため、 このディレクトリは-Iスイッチを使用してパス読み込みが追加されている必要がある事に注意してください。

上記の.(ドット)は、テストをパスしたことを指し示します。 テストが失敗した際はFを、テストがエラーをスローした際はEをその場所に表示します。 最後の行は、テスト結果の要約になります。

テスト失敗がどのようにレポートされるか確認するために、post_test.rbに失敗するテストを追加してみましょう。

test "should not save post without title" do
  post = Post.new
  assert !post.save
end

追加されたテストを実行してみます。

$ rake test test/models/post_test.rb test_should_not_save_post_without_title
F

Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.

  1) Failure:
test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
Failed assertion, no message given.

1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

失敗を意味するFが出力されました。 1)のすぐ下に失敗したテスト名が表示されているのが確認出来ます。 その次の数行に、スタックトレースを含む実際の値とアサーションによって期待されていた値への言及が続きます。 デフォルトのアサーションメッセージは、エラー箇所を特定する十分な情報を提供してくれます。 アサーションの失敗メッセージを読み易くするように、 下記のようにして任意のメッセージパラメータを各アサーションに追加します。

test "should not save post without title" do
  post = Post.new
  assert !post.save, "Saved the post without a title"
end

このテストを実行すると、下記のように分かり易いアサーションメッセージが表示されます。

  1) Failure:
test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
Saved the post without a title

それではこのテストをパスさせるために、モデル層にtitleフィールドの検証を追加してみましょう。

class Post < ActiveRecord::Base
  validates :title, presence: true
end

これでテストはパスするはずです。 再度テストを実行して、確認してみましょう。

$ rake test test/models/post_test.rb test_should_not_save_post_without_title
.

Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

望み通りに書いたテストが失敗し、コードを追加することで望み通りにパスすることが出来ました。 ソフトウェア開発でのこのような手法を、テスト駆動開発(TDD)と呼んでいます。

多くのRails開発者は、TDDを実践しています。 これは、アプリケーションのあらゆる部分でテストを行う優れたテストスイートの構築方法です。 TDDの内容はこのガイドの範疇に収まりません。 15のTDDステップによるRails開発(英文)などを参考にしてください。

どのようにエラーがレポートされるのかを、下記のエラーを含むテストで確認してみましょう。

test "should report error" do
  # some_undefined_variableは、
  # テストケース内のどこにも定義されていません。
  some_undefined_variable
  assert true
end

それでは、テストを実行してコンソール内の出力を確認してみましょう。

$ rake test test/models/post_test.rb test_should_report_error
E

Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.

  1) Error:
test_should_report_error(PostTest):
NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0>
    test/models/post_test.rb:10:in `block in <class:PostTest>'

1 tests, 0 assertions, 0 failures, 1 errors, 0 skips

エラーを意味する'E'が出力され、テストがエラーになったことが確認出来ます。

各テストメソッドの実行は、何らかのエラーまたはアサーションの失敗があると停止し、 次のメソッドのテストスイートを続けます。 全てのテストメソッドはアルファベット順に実行されます。

3.3 何をユニットテストに含めるか?

エラーとなる可能性があるもの全てを、テストを含める事が理想的です。 モデル内の各検証(validation)に少なくとも1つ、各メソッドに少くとも1つ、それぞれテストを持つことが良い慣習と言えます。

3.4 利用可能なアサーション

これまでに幾つかの利用可能なアサーションを見てきました。 アサーションはテストの働き蜂のようなもので、期待どおりに実行されるのかを実際に実行して確認します。

様々なタイプのアサーションが用意されています。 下記は、アサーションからRailsでデフォルトで利用可能なMiniTestのものを抜き出したリストです。 [msg]パラメータには、テストが失敗した際に明確に分かるように任意の文字列メッセージを指定します。 これは必須ではありません。

アサーション 目的
assert( test, [msg] ) testがtrueであることを確認します。
assert_not( test, [msg] ) testがfalseであることを確認します。
assert_equal( expected, actual, [msg] ) expected == actualがtrueであることを確認します。
assert_not_equal( expected, actual, [msg] ) expected != actualがtrueであることを確認します。
assert_same( expected, actual, [msg] ) expected.equal?(actual)であることを確認します。
assert_not_same( expected, actual, [msg] ) expected.equal?(actual)がfalseであることを確認します。
assert_nil( obj, [msg] ) obj.nil?がtrueであることを確認します。
assert_not_nil( obj, [msg] ) obj.nil?がfalseであることを確認します。
assert_match( regexp, string, [msg] ) 正規表現がstringにマッチすることを確認します。
assert_no_match( regexp, string, [msg] ) 正規表現がstringにマッチしないことを確認します。
assert_in_delta( expecting, actual, [delta], [msg] ) expectedactualの数値の差がdelta以内かを確認します。
assert_not_in_delta( expecting, actual, [delta], [msg] ) expectedactualの数値の差がdelta以内では無い事を確認します。
assert_throws( symbol, [msg] ) { block } 与えられたブロックがsymbolをスローするか確認します。
assert_raises( exception1, exception2, ... ) { block } 与えられたブロックが、与えられた例外の1つを発生させるかを確認します。
assert_nothing_raised( exception1, exception2, ... ) { block } 与えられたブロックが、与えられた例外の1つを発生させないことを確認します。
assert_instance_of( class, obj, [msg] ) objclassのインスタンスであることを確認します。
assert_not_instance_of( class, obj, [msg] ) objclassのインスタンスでは無いことを確認します。
assert_kind_of( class, obj, [msg] ) objclass、またはその子孫であることを確認します。
assert_not_kind_of( class, obj, [msg] ) objclass、またはその子孫では無いことを確認します。
assert_respond_to( obj, symbol, [msg] ) objsymbolを応答することを確認します。
assert_not_respond_to( obj, symbol, [msg] ) objsymbolを応答しないことを確認します。
assert_operator( obj1, operator, [obj2], [msg] ) obj1.operator(obj2)がtrueであることを確認します。
assert_not_operator( obj1, operator, [obj2], [msg] ) obj1.operator(obj2)がfalseであることを確認します。
assert_send( array, [msg] ) array[0]のオブジェクト上のarray[1]にリストされているメソッドをarray[2以降]の引数付きで実行し、 trueであることを確認します。(翻訳に自信なし)
flunk( [msg] ) 失敗することを保証します。 これはまだ終了していないテストであることを明示的にマークするのに便利です。

テストフレームワークのモジュール性を高めるために、自身のアサーションを作成することが可能です。 実際にRailsはまさにそれを行い、これには開発者の生活をより良くしてくれる特別なアサーションも含まれることでしょう。

自身のアサーションを作成するより進んだトピックは、このチュートリアルでは説明しきれません。

3.5 Rails独自のアサーション

Railsは自身が所有する幾つかのカスタムアサーションをtest/unitフレームワークに追加します。

アサーション 目的
assert_difference(expressions, difference = 1, message = nil) {...} ブロックによって評価された結果としての式の返り値の数の差分を検証します。
assert_no_difference(expressions, message = nil, &amp;block) 渡したブロック実行前後で、式によって評価される結果の数値に変化が無いことを検証します。
assert_recognizes(expected_options, path, extras={}, message=nil) 与えられたpathの経路(Route)が正しく扱われており、 解析されたオプション(与えられたexpected_optionsハッシュ内の)がpathにマッチすることを確認します。 つまり、expected_optionsによって与えられた経路をRailsが認識することを確認します。
assert_generates(expected_path, options, defaults={}, extras = {}, message=nil) 提供されたoptionsが、提供されたパスの生成に使用出来ることを確認します。 これはassert_recognizesと正反対になります。 extrasパラメータはリクエストにその名前とクエリー文字となる追加リクエストパラメータの値を伝える際に使用されます。 messageパラメータは、アサーション失敗時に任意のエラーメッセージを出力するためのものです。
assert_response(type, message = nil) レスポンスが指定したステータスコードになることを確認します。 下記は指定可能なステータスとその範囲です。
:success
200 ~ 299
:redirect
300 ~ 399
:missing
404
:error
500 ~ 599
assert_redirected_to(options = {}, message=nil) 最終的にリダイレクトが行われるのかを確認します。 assert_redirected_to(controller: "weblog")のような部分的なマッチが可能で、 redirect_to(controller: "weblog", action: "show")などのリダイレクトにマッチします。
assert_template(expected = nil, message=nil) リクエストが適切なテンプレートファイルを使用して描画されることを確認します。

次の章で、これらのアサーションの使用方法を確認しましょう。

4. コントローラーの機能テスト

Railsでは、単一のコントローラーの様々なアクションをテストする事を、「コントローラーのための機能テストを書く」と呼びます。 コントローラーは受け取ったリクエストを取り扱い、最終的にビューを描画することで応答します。

4.1 機能テストには何が含まれるか

機能テストでは、下記のようなことをテストすべきです。

  • そのリクエストは成功(success)したか?
  • ユーザーは正しいページにリダイレクトされたか?
  • ユーザーの認証は成功したか?
  • 応答テンプレート内に正しいオブジェクトは格納されたか?
  • ビュー内にユーザーへの適切なメッセージが表示されたか?

先程、Railsのスキャフォールドを使用してPostリソースを生成したのであれば、 その中にコントローラーのコードとそのテストも作成されているはずです。 test/controllersディレクトリ内から、posts_controller_test.rbファイルを確認できるはずです。

では、posts_controller_test.rbファイルから、 test_should_get_indexを確認してみましょう。

class PostsControllerTest < ActionController::TestCase
  test "should get index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:posts)
  end
end

test_should_get_indexテスト内で、Railsはindexアクションを呼び出すリクエストをシミュレートし、 リクエストが成功する事と有効なpostsインスタンスが割り当てられている事を確認します。

getメソッドはリクエストをキックし、レスポンス結果を取得します。 このメソッドは4つの引数を受け取る事が出来ます。

  • リクエストするコントローラーのアクションです。 文字列またはシンボルで指定可能です。
  • アクションへ渡すリクエストパラメータの任意のハッシュです。(例: クエリー文字列、またはPOST値)
  • リクエストに沿って渡すセッション変数の任意のハッシュです。
  • flash値の任意のハッシュです。
# (例) idが12、user_idセッションが5の:showアクションの呼び出し
get(:show, {'id' => "12"}, {'user_id' => 5})
# (例) :idが12、セッション無し、flashのメッセージ付きのviewアクションを呼び出し
get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})

もし、posts_controller_test.rbから、test_should_create_postテストの実行を試みた場合、 このままでは、当然モデルの新規追加の検証の層で失敗します。

posts_controller_test.rb内のtest_should_create_postテストを修正し、 全てのテストがパスするようにしてみましょう。

test "should create post" do
  assert_difference('Post.count') do
    post :create, post: {title: 'Some title'}
  end

  assert_redirected_to post_path(assigns(:post))
end

これで、全てのテストがパスされるはずです。

4.2 機能テストに利用可能なリクエストタイプ

もし、HTTPプロトコルに慣れ親しんでいるのであれば、getがリクエストのタイプであることはご存知でしょう。 Railsの機能テストでは、下記の6つのリクエストのタイプがサポートされています。

  • get
  • post
  • patch
  • put
  • head
  • delete

全てのリクエストのタイプを使用することが出来ますが、 頻繁に使用するのはおそらく1つ目と2つ目のgetとpostになるでしょう。

機能テストでは、指定したリクエストのタイプがアクションによって受け入れられるべきか否かを検証することはしません。 ここでのリクエストタイプは、テストの内容をより分かり易くするために存在します。

4.3 4つのハッシュ

リクエストが6つのメソッド(get、post他)の内、いずれか1つが作成、処理されると、 4つのハッシュオブジェクトが使用できるようになります。

  • assigns - ビュー内で使用するために、アクション内でインスタンス変数として格納されるオブジェクトです。
  • cookies - Cookieがセットされます。
  • flash - 有効なflashのオブジェクトです。
  • session - 有効なセッション変数のオブジェクトです。

通常のハッシュオブジェクトであるため、文字列キーの参照によって値にアクセスすることが出来ます。 また、シンボル名によっても参照することが可能です。 例えば、

flash["gordon"]               flash[:gordon]
session["shmession"]          session[:shmession]
cookies["are_good_for_u"]     cookies[:are_good_for_u]

# 歴史的な事情から、assigns[:something]を使用することは出来ません。
assigns["something"]          assigns(:something)

4.4 使用可能なインスタンス変数

機能テスト内で3つのインスタンス変数にアクセスすることも可能です。

  • @controller - リクエストを処理するコントローラーです。
  • @request - リクエストです。
  • @response - レスポンスです。

4.5 ヘッダーとCGI変数の設定

ヘッダーとCGI変数んは@requestインスタンス変数上で直接設定することが可能です。

# HTTPヘッダーの設定
@request.headers["Accept"] = "text/plain, text/html"
get :index # カスタムヘッダーによるリクエストのシミュレート

# CGI変数の設定
@request.headers["HTTP_REFERER"] = "http://example.com/home"
post :create # カスタム環境変数によるリクエストのシミュレート

4.6 テンプレートとレイアウトのテスト

レスポンスが正しいテンプレートとレイアウトを描画していることを確認したい場合、 assert_templateメソッドを使用することが出来ます。

test "index should render correct template and layout" do
  get :index
  assert_template :index
  assert_template layout: "layouts/application"
end

assert_templateの1回の呼び出しで、 同時にテンプレートとレイアウトをテスト出来ないことに注意してください。 文字列の代わりに正規表現をlayoutのテストに使用することも可能です。 ただし、明確な文字列を指定するようにしてください。 また、レイアウトファイルを標準のレイアウトディレクトリ内に保存していたとしても、 "layout"ディレクトリ名を含める必要があります。そのため、

assert_template layout: "application"

上記は正常に機能しません。

もし、レイアウトをアサートする際に、ビューがpartialを描画する場合、 同時にpartialをアサートする必要があります。 それを行わないと、アサーションが失敗します。そのため、

test "new should render correct layout" do
  get :new
  assert_template layout: "layouts/application", partial: "_form"
end

上記は、_formと名付けられたpartialのビューを描画する際のレイアウトのための正しいアサーションの方法になります。 assert_template呼び出しで、:partialキーを省略すると警告されます。

4.7 全体を通しての機能テストの例

下記は、flashassert_redirected_toassert_differenceを使用した別の例になります。

test "should create post" do
  assert_difference('Post.count') do
    post :create, post: {title: 'Hi', body: 'This is my first post.'}
  end
  assert_redirected_to post_path(assigns(:post))
  assert_equal 'Post was successfully created.', flash[:notice]
end

4.8 ビューのテスト

HTML要素内にキーが存在することをアサーションすることで、 リクエストに対するレスポンスとなるビューの内容をテストする事が出来ます。 assert_selectアサーションは、シンプル且つ強力な文法によってこれを行います。

別のドキュメントでassert_tagを参照しているかもしれませんが、 現在これは非推奨になっています。

assert_selectには、2つの書き方が存在します。

assert_select(selector, [equality], [message])は、 equalityの条件がselectorを通して選択された要素に含まれるかを確認します。 このselectorには、CSSセレクタ表記(文字列)、代替値を持つ式、HTML::Selectorオブジェクトのいずれかを指定することが出来ます。

assert_select(element, selector, [equality], [message])は、 equalityの条件がelement(HTML::Nodeのインスタンス)と、 その子孫からのselectorを通して選択された全ての要素に含まれるかを確認します。

例えば、レスポンスのtitle要素の内容を下記のようにして確認することが出来ます。

assert_select 'title', "Railsテストのガイドへようこそ"

また、入れ子のassert_selectブロックを使用することも可能です。 下記のケースでは、外部のassert_selectブロックによって選択された要素内で、 内部のassert_selectによるコレクション全てのアサーションを行います。

assert_select 'ul.navigation' do
  assert_select 'li.menu_item'
end

別の方法として、外部のassert_selectによって選択された要素のコレクションは、 各要素毎に別々に呼び出してassert_selectを通して繰り返し処理することが可能です。 レスポンスに2つのOL要素が含まれており、それぞれ4つのLI要素を持っていれば、 下記のテストに両方ともパスすることが出来ます。

#それぞれOL要素で、LI要素を4つずつ
assert_select "ol" do |elements|
  elements.each do |element|
    assert_select element, "li", 4
  end
end

#全体でLI要素が8つ
assert_select "ol" do
  assert_select "li", 8
end

assert_selectは、非常に強力なアサーションです。 更に詳細を知りたければ、assert_selectのドキュメントを参照してください。

4.8.1 ビュー基盤のアサーションの追加

ビューのテストに使用される主要なアサーションは、他にも存在します。

アサーション 目的
assert_select_email メールの本文のアサーションを行います。
assert_select_encoded HTMLエンコード(符号化)のアサーションを行います。 各要素の内容のエンコードを元(HTML)に戻し、 次に全ての要素のエンコードを戻したものを使用してそのブロックを確かめることで、 これを行います。
css_select(selector)
または
css_select(element, selector)
selectorによって選択された全ての要素の配列を返します。 2つ目の使い方では、elementの要素を基準に、 その子要素内でのselector式のマッチを試みます。 どちらの使い方でもマッチするものが無ければ、空の配列を返します。

下記は、assert_select_email:の使用例です。

assert_select_email do
  assert_select 'small', 'Please click the "Unsubscribe" link if you want to opt-out.'
end

5. 統合テスト

統合(Integration)テストは、複数のコントローラー間でのやり取りをテストするのに使用されます。 一般的にアプリケーションの重要なワークフローをテストする際に使用されます。

ユニットテスト、機能テストとは異なり、統合テストは明確に'test/integration'フォルダ内に作成する必要があります。 Railsは統合テストを作成するためのジェネレーターを提供してくれます。

$ rails generate integration_test user_flows
      exists  test/integration/
      create  test/integration/user_flows_test.rb

生成された直後のテストは、下記のようになります。

require 'test_helper'

class UserFlowsTest < ActionDispatch::IntegrationTest
  # test "the truth" do
  #   assert true
  # end
end

統合テストは、ActionDispatch::IntegrationTestを継承します。 これにより、統合テスト内で幾つかのヘルパーが利用出来るようになります。 また、fixtureをテストで利用出来るように、明示的に取り込む必要があります。

5.1 統合テストで利用可能なヘルパー

標準のテストヘルパーに加え、統合テストでは幾つかの追加ヘルパーを使用することが出来ます。

ヘルパー 目的
https? もしセッションがHTTPSリクエストに見せかけた状態であれば、trueを返します。
https! HTTPSに見せかけたリクエストを行ってくれます。
host! 次のリクエストで、ホスト名を設定できるようにしてくれます。
redirect? 最後のリクエストがリダイレクトされたのであれば、trueを返します。
follow_redirect! 単一のリダイレクトのレスポンスに従います。
request_via_redirect(http_method, path, [parameters], [headers]) HTTPリクエストと、それに続くリダイレクトを発生させます。
post_via_redirect(path, [parameters], [headers]) HTTPのPOSTリクエストと、それに続くリダイレクトを発生させます。
get_via_redirect(path, [parameters], [headers]) HTTPのGETリクエストと、それに続くリダイレクトを発生させます。
patch_via_redirect(path, [parameters], [headers]) HTTPのPATCHリクエストと、それに続くリダイレクトを発生させます。
put_via_redirect(path, [parameters], [headers]) HTTPのPUTリクエストと、それに続くリダイレクトを発生させます。
delete_via_redirect(path, [parameters], [headers]) HTTPのDELETEリクエストと、それに続くリダイレクトを発生させます。
open_session 新しいセッションインスタンスを開きます。

5.2 統合テストの例

下記は複数のコントローラーを通す、シンプルな統合テストの例です。

require 'test_helper'

class UserFlowsTest < ActionDispatch::IntegrationTest
  fixtures :users

  test "login and browse site" do
    # HTTPSを経由してログイン
    https!
    get "/login"
    assert_response :success

    post_via_redirect "/login", username: users(:david).username, password: users(:david).password
    assert_equal '/welcome', path
    assert_equal 'Welcome david!', flash[:notice]

    https!(false)
    get "/posts/all"
    assert_response :success
    assert assigns(:products)
  end
end

複数のコントローラーとデータベースアクセスから遷移まで含まれた、 統合テストを確認出来ると思います。 加えて、テスト内で複数のセッションインスタンスを開くシミュレーションを行うことが可能で、 非常に強力なテストDSL(ドメイン特化言語)を作成するためのアサーションメソッドを使用して、それらのインスタンスを拡張します。

下記は、統合テストでの複数のセッション例とカスタムDSLの例です。

require 'test_helper'

class UserFlowsTest < ActionDispatch::IntegrationTest
  fixtures :users

  test "login and browse site" do

    # davidユーザーのログイン
    david = login(:david)
    # guestユーザーのログイン
    guest = login(:guest)

    # 異なるセッションの両方が利用可能
    assert_equal 'Welcome david!', david.flash[:notice]
    assert_equal 'Welcome guest!', guest.flash[:notice]

    # davidユーザーでサイトをブラウジング
    david.browses_site
    # 同様にguestユーザーでサイトをブラウジング
    guest.browses_site

    # 引き続き、その他のアサーションを行う
  end

  private

  module CustomDsl
    def browses_site
      get "/products/all"
      assert_response :success
      assert assigns(:products)
    end
  end

  def login(user)
    open_session do |sess|
      sess.extend(CustomDsl)
      u = users(user)
      sess.https!
      sess.post "/login", username: u.username, password: u.password
      assert_equal '/welcome', path
      sess.https!(false)
    end
  end
end

6. Rakeによるテスト実行

テスト毎に、手作業でセットアップと実行を行う必要はありません。 Railsには、テストを手助けするためのコマンドが用意されています。 下記のテーブルのリストは、Railsプロジェクトの初期化を行った際にデフォルトのRakefileに取り込まれる全コマンドになります。

タスク 説明
rake test 全てのユニット、機能、統合テストを実行します。
rake test:controllers test/controllersから、全てのコントローラーテストを実行します。
rake test:functionals test/controllerstest/mailerstest/functionalから、 全ての機能テストを実行します。
rake test:helpers test/helpersから、全てのヘルパーテストを実行します。
rake test:integration test/integrationから、全ての統合テストを実行します。
rake test:mailers test/mailersから、全てのメーラーテストを実行します。
rake test:models test/modelsから、全てのモデルテストを実行します。
rake test:units test/modelstest/helperstest/unitから、 全てのユニットテストを実行します。

rakeタスク実行によって、初期化することが出来るテストコマンドも存在します。

タスク 説明
rake test 全てのユニット、機能、統合テストを実行します。
rake test:recent 直近の変更をテストします。
rake test:uncommitted 未コミットの全てのテストを実行します。 SubversionとGitをサポートします。

7. MiniTestについての簡単な注意

Rubyには多くのライブラリが積み込まれています。 Ruby1.8では、RubyでのユニットテストのためのTest::Unitを提供します。 これまで説明してきた基本的なアサーションの全ては、実際にはTest::Unit::Assertions内に実際に定義されているものです。 ActiveSupport::TestCaseクラスは、Test::Unit::TestCaseを拡張して、 ユニット、機能テストで使用され、基本的なアサーション全てを使用出来るようにしてくれます。

Ruby 1.9では、Test::Unitのバージョンを更新し、 裏でTest::Unitとの互換処理を提供するMiniTestが導入されました。 Ruby1.8でも、minitestをインストールすることでMinitestを使用することが出来ます。

更にTest::Unitの詳細を知りたければ、 test/unitを参照してください。
更にMiniTestの詳細を知りたければ、 Minitestを参照してください。

8. setupとteardown

各テストの前または後に、実行したいコードのブロックがそれぞれある場合、 それを実現してくれる2つの特別なコールバックを使用することが出来ます。 Postコントローラー内の機能テストに、下記を追記してみましょう。

require 'test_helper'

class PostsControllerTest < ActionController::TestCase

  # 各のテストの前に呼び出し
  def setup
    @post = posts(:one)
  end

  # 各テストの後に呼び出し
  def teardown
    # 各テストの前に、ここでnilを設定することで@postを再初期化しておきます。
    # これは必須ではありませんが、teardownメソッドの使用方法について、
    # 理解しておいてください。
    @post = nil
  end

  test "should show post" do
    get :show, id: @post.id
    assert_response :success
  end

  test "should destroy post" do
    assert_difference('Post.count', -1) do
      delete :destroy, id: @post.id
    end

    assert_redirected_to posts_path
  end

end

上記のコードでは、setupメソッドが各テスト前に呼び出され、 @postが各テストで利用可能なります。 Railsは、ActiveSupport::Callbacksとしてsetupteardownを実装しており、 必ずしもメソッドでsetupteardownを使用する必要は無いという事を意味します。 これらは下記の方法で使用することが可能です。

  • ブロック
  • メソッド(上記の例のような)
  • シンボルとしてのメソッド名
  • Lambda

上記の例のsetupコールバックを、シンボルとしてのメソッド名にしたものを確認してみましょう。

require 'test_helper'

class PostsControllerTest < ActionController::TestCase

  # 各テストの前に呼び出し
  setup :initialize_post

  # 各テストの後に呼び出し
  def teardown
    @post = nil
  end

  test "should show post" do
    get :show, id: @post.id
    assert_response :success
  end

  test "should update post" do
    patch :update, id: @post.id, post: {}
    assert_redirected_to post_path(assigns(:post))
  end

  test "should destroy post" do
    assert_difference('Post.count', -1) do
      delete :destroy, id: @post.id
    end

    assert_redirected_to posts_path
  end

  private

  def initialize_post
    @post = posts(:one)
  end

end

9. Routesのテスト

これまでテストしてきたものに加え、Railsの経路(Routes)テストも行うことをお勧めします。 上記の例において、デフォルトでPostsコントローラーのshowアクション経路のテストは、下記のようになっているはずです。

test "should route to post" do
  assert_routing '/posts/1', {controller: "posts", action: "show", id: "1"}
end

10. メーラーのテスト

メーラークラスのテストは、それを行うためのある特定のツールを必要とします。

10.1 確認中のPostmanの保持

メーラークラスも他の部分と同様に期待通りに動作するかのテストを行うべきです。

下記は、メーラークラスのテストで確認すべきことになります。

  • メールが処理されていること(作成と送信)
  • メールの内容が正しいこと(件名、送り先、本文、その他)
  • 正しいメールが正しい時間に送信されていること
10.1.1 メーラーの2種類のテストについて

メーラーのテストには、ユニットテストと機能テストという2つの側面があります。 ユニットテストでは、コントロールの入力と切り離ししてメールを実行し、 出力される値を既知(fixture)の値と比較します。 機能テストは、メールによって生成される内容を詳細にテストするものではありませんが、 その代わりに正しい方法でコントローラーとモデルがメーラーを使用しているかをテストします。 正しい時間に正しいメールが送信されていることを確認します。

10.2.1 fixturesについて

メーラーのユニットテストを行う目的で、fixturesがどのように出力されるべきかのサンプルを提供するために使用されます。 これらはサンプルのメールであり、また他のfixturesのようなActive Recordデータでは無いため、 それら自身のサブディレクトリに保持します。 test/fixtures内のディレクトリ名は、メーラーの名前に相当する名前になります。 例えば、UserMailerという名前のメーラーであれば、fixturesはtest/fixtures/user_mailerディレクトリ内に置かれるべきです。

メーラーを生成した際に、ジェネレーターはstub fixturesを各メーラーアクションのために生成してくれます。 もし、ジェネレーターを使用しなかった場合は、自分でそれらのファイルを作る必要があります。

10.2.2 基本的なテストについて

下記は、UserMailerという名前のメーラーが持つフレンドに招待メールを送信するのに使用される、 inviteアクションのユニットテストです。 これはinviteアクションのために、ジェネレーターによって生成されたテストの基本的な内容になります。

require 'test_helper'

class UserMailerTest < ActionMailer::TestCase
  tests UserMailer
  test "invite" do
    # メール送信後、キュー(送信保留)が無いか確認します。
    email = UserMailer.create_invite('[email protected]',
                                     '[email protected]', Time.now).deliver
    assert !ActionMailer::Base.deliveries.empty?

    # 送信メールの本文の内容が期待通りなのかを確認します
    assert_equal ['[email protected]'], email.from
    assert_equal ['[email protected]'], email.to
    assert_equal 'You have been invited by [email protected]', email.subject
    assert_equal read_fixture('invite').join, email.body.to_s
  end
end

下記は、inviteのfixtureの内容です。

Hi [email protected],

You have been invited.

Cheers!

これは、メーラーのテストの書き方について理解を深める良い機会です。 config/environments/test.rb内のActionMailer::Base.delivery_method = :testの行は、 deliveryメソッドをテストモードにし、実際にメールが送信されないように(テスト中にユーザーに対してメール送信することを避けるため)し、 代わりに配列に追加します。(ActionMailer::Base.deliveries)

そのActionMailer::Base.deliveries配列は、ActionMailer::TestCaseテストでのみ自動的にリセットされます。 もし、Action Mailerテスト外で白紙の状態にしたいのであれば、ActionMailer::Base.deliveries.clearを使用して、 手動でそれを行います。

10.3 機能テスト

メーラーの機能テストでは、単にメール本文と受取人が正しいという事の他にも、様々なことをテストします。 機能テストでは、メール送信メソッドを呼び出して適切なメールが配送リストに追加される事を確認します。 deliverメソッドが自身の処理を正しく行うと過程するのであれば、これで適切に処理されていると確認出来ます。(翻訳に自信なし) おそらく、メールが送信されるべき時に、自分の書いたビジネスロジックがメールを送信しているかどうか気になると思いますが、 例えば、フレンドへの招待メール操作が適切なメールを送信しているかを確認することが出来ます。

require 'test_helper'

class UserControllerTest < ActionController::TestCase
  test "invite friend" do
    assert_difference 'ActionMailer::Base.deliveries.size', +1 do
      post :invite_friend, email: '[email protected]'
    end
    invite_email = ActionMailer::Base.deliveries.last

    assert_equal "You have been invited by [email protected]", invite_email.subject
    assert_equal '[email protected]', invite_email.to[0]
    assert_match(/Hi [email protected]/, invite_email.body)
  end
end

11. Helperのテスト

ヘルパーをテストするのに必要な事は、ヘルパーメソッドが出力するものが期待通りかどうかを確認するだけです。 ヘルパーに関するテストは、test/helpersディレクトリ下に格納します。 Railsには、ヘルパーとそのテストファイルを生成するジェネレーターのコマンドが備わっています。

$ rails generate helper User
      create  app/helpers/user_helper.rb
      invoke  test_unit
      create    test/helpers/user_helper_test.rb

生成されたテストファイルには、下記のコードが含まれます。

require 'test_helper'

class UserHelperTest < ActionView::TestCase
end

ヘルパーは、ビューで利用可能なメソッドを開発者がメソッドとして定義する事が出来るシンプルなモジュールです。 ヘルパーのメソッドの出力をテストするには、下記のようにヘルパーを読み込む必要があります。

class UserHelperTest < ActionView::TestCase
  include UserHelper

  test "should return the user name" do
    # ...
  end
end

更にtestクラスはActionView::TestCaseを継承するため、 link_tolink_toのようなRailsのヘルパーメソッドにアクセスすることが出来ます。

12. その他のテスト手法

組み込みのtest/unitによるテストだけが、Railsアプリケーションのテストを行えるというわけではありません。 Rails開発者は、下記に含まれるような様々な手法を用いてテストを行い、またそれらの援助にも取り組んでいます。

  • NullDB - データベースの使用を避けることで、テストをスピードアップします。
  • Factory Girl - fixturesの代わりとなる仕組みです。
  • Machinist - もう一つのfixturesを代わりとなる仕組みです。
  • Fixture Builder - テスト実行前にfixturesを生成してくれるツールです。
  • MiniTest::Spec Rails - RailsテストでMiniTest::SpecのDSLを使用します。
  • Shoulda - test/unitの拡張で、ヘルパー、マクロ、アサーションを追加します。
  • RSpec - BDD(ビヘイビア駆動開発)のフレームワークです。

 Back to top

© 2010 - 2017 STUDIO KINGDOM