Railsはデフォルトではminitestでテストを書くようになっていて、テストは
- Unit Test (
test/models
) - Functional Test (
test/controllers
) - Integration Test (
test/integration
)
に分類されています。
Unit TestとIntegration Testは良いんですが、端的に言えば、Functional Testは書かないのが良いです。コントローラに書いたコードをテストしたい場合は、Integration Testを書きましょう。
Rails 4までと、Rails 5以降では状況が変わってくるみたいです。
Rails 5をお使いの場合は、深く考えずにRailsがg
してくれたファイルにテストを書いて大丈夫です。
理由
Functional Testを書いていくと、実際にWebアプリとして動作させる場合との挙動の違いに悩むことがあります。
-
params
の扱いが本物のWebアプリケーションと違うことがある - 一個のテストケースでは一個のコントローラのインスタンスが使い回されるので、テストの書き方とコントローラの実装によっては、本物のWebアプリケーションと挙動が変わる
もちろんこれらに注意しながらテストを書くこともできますが、特に1.にはまるとけっこう辛いので、雑に「Functional Testは書かない」という方針を採用するのが良いんじゃないかと思います。
params
の扱い
JSONをPOSTするようなAPIのときにparams
が本物と違って困ることがあります。
test "post JSON" do
post(:do_something, { id: 123 })
end
こんなテストでは、コントローラから見たときに、params[:id]
が123
になっていて欲しいのですが、実際には"123"
と文字列になってしまいます。Functional Testを使うと、params
の値は全部文字列になり、ふつーにWebアプリとして動作させたときにはきちんとJSONの通りの値になります。
POSTされたJSONをそのままActiveRecordに引き渡すような場合には、ActiveRecordが上手いことやってくれるので問題にならないのですが、params
から数字を読んできて計算しなくてはいけない場合とかに困ります。
注意が必要ですね。
インスタンス使い回しの問題
Railsで作られたWebアプリケーションでは、リクエスト一つ一つを処理するためにコントローラのインスタンスが新しく作られます。Functional Testでは一つのテストケースでは一つのコントローラのインスタンスが使い回されます。
行儀良くテストを書いていれば一つのテストケースの中で複数回アクションを呼ぶことはないでしょうし、行儀良くコントローラを実装していればアクション起動前のインスタンス変数の状態に依存するコードは書かないでしょうが、うっかりこの二つの条件に引っかかるテストとコントローラを書いてしまうと、テストが通るのに動作しないWebアプリケーションができてしまいます。
params
の問題に比べると仮定が多くて、実際に問題のあるコード・テストになってしまうことは少ないとは思いますが、僕は実際に経験したことがあります。
- 一つのテストケースの中でアクションを複数回呼ばない
- アクションの中では依存するインスタンス変数に注意する
などのポイントに気を付けることもできますが、まーこういうのが問題になるのって気を付けていても見落とす場合ですよね。
まとめ
RailsのFunctional Testは注意するべき点がいくつかあるので、細心の注意を払ってテストを書かなくてはいけません。テストごときに細心の注意とか払いたくないし多分そのうち忘れて悲しいことになるので、雑な対処として「Functional Testは書かない」というのをお勧めします。
rspec-railsとかだとどうなってるんでしょうね。よく知りません。
Rails5ではどうなるか
5.0.0.rc1
で試してみたところ、test/functionals
以下にできるファイルはActionDispatch::IntegrationTest
のサブクラスになっていました。Rails 4まではActionController::TestCase
のサブクラスだったので、変更されているようです。
ここで説明したつらさはすべてActionController::TestCase
に由来するものなので、Rails 5を使う場合は特に悩む必要がないようです。
- Deprecate ActionController::TestCase in favor of ActionDispatch::IntegrationTest - https://github.com/rails/rails/issues/22496