Railsのテスト
このガイドでは、アプリケーションのためのRails組み込みテストについて説明します。 このガイドを読むことで、次の事が学べるはずです。
- Railsでのテストの専門用語
- アプリケーションのユニット、機能、統合テストの書き方について
- 他の一般的なテスト手法やプラグイン
- 1. 何故テストを書くのか?
- 2. テストの導入について
- 3. モデルのユニットテスト
- 4. コントローラーの機能テスト
- 5. 統合テスト
- 6. Rakeによるテスト実行
- 7. MiniTestについての簡単な注意
- 8. setupとteardown
- 9. Routesのテスト
- 10. メーラーのテスト
- 11. Helperのテスト
- 12. その他のテスト手法
1. 何故テストを書くのか?
Railsは、テストを書くことを非常に簡単にしてくれます。 モデルとコントローラー作成の際に生成されたスケルトンを使って始めてみましょう。
単にRailsテストを実行することによって、主要なコードのリファクタリングの後でも、 コードが望むような動きをすることを保証することが出来ます。
Railsはブラウザのリクエストをシミュレートすることも可能であるため、 アプリケーションのレスポンスのテストをブラウザを使用すること無く行うことが可能です。
2. テストの導入について
テストをサポートする機能は、最初からRailsに組み込まれています。 "流行っていてクールだから取り入れよう!"という理由で組み込まれたわけではありません。 ほぼ全てのRailsアプリケーションには、データベースとのやり取りが頻繁にあるため、 同様にテストでもデータベースとのやり取りが必要になります。 効率的なテストを書くために、このデータベースの設定とデータを問入れる方法について理解する必要があるでしょう。
2.1 テスト環境
デフォルトでRailsアプリケーションは、development
、test
、production
の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_to
とhas_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_password
、test_valid_password
、testValidPassword
の全ては、
定められたテスト名であり、テストケース実行時に自動的に実行されます。
Railsはテスト名とブロックを取得するtest
メソッドを追加します。
これはメソッド名の接頭辞にtest_
が付く通常のMiniTest::Unitテストを生成します。
そのため、
test "the truth" do
assert true
end
上記は、下記のように書いたものとして振る舞います。
def test_the_truth
assert true
end
テストマクロのみの方が、よりテスト名が読みやすいものになりますが、 通常のメソッド定義を使用することも可能です。
メソッド名は、アンダースコアを空白に置き換えて生成されます。
その結果が、有効なRuby識別子である必要はありませんが、
その名前には句読点などが含まれているかもしれません。
Rubyでは、専門的な(技術的な?)な文字列が、メソッド名になることがあるからです。
これらの変わったものは、define_method
とsend
の呼び出しが必要ですが、形式上の制約はありません。
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 |
現在の |
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.rb
はtest
ディレクトリ内にあるため、
このディレクトリは-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] ) |
expected とactual の数値の差がdelta 以内かを確認します。
|
assert_not_in_delta( expecting, actual, [delta], [msg] ) |
expected とactual の数値の差が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] ) | obj がclass のインスタンスであることを確認します。 |
assert_not_instance_of( class, obj, [msg] ) | obj がclass のインスタンスでは無いことを確認します。 |
assert_kind_of( class, obj, [msg] ) | obj がclass 、またはその子孫であることを確認します。 |
assert_not_kind_of( class, obj, [msg] ) | obj がclass 、またはその子孫では無いことを確認します。 |
assert_respond_to( obj, symbol, [msg] ) | obj がsymbol を応答することを確認します。 |
assert_not_respond_to( obj, symbol, [msg] ) | obj がsymbol を応答しないことを確認します。 |
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, &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) | レスポンスが指定したステータスコードになることを確認します。 下記は指定可能なステータスとその範囲です。 |
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 全体を通しての機能テストの例
下記は、flash
、assert_redirected_to
、
assert_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/controllers 、test/mailers 、test/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/models 、test/helpers 、test/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としてsetup
とteardown
を実装しており、
必ずしもメソッドでsetup
とteardown
を使用する必要は無いという事を意味します。
これらは下記の方法で使用することが可能です。
- ブロック
- メソッド(上記の例のような)
- シンボルとしてのメソッド名
- 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_to
やlink_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(ビヘイビア駆動開発)のフレームワークです。
© 2010 - 2017 STUDIO KINGDOM