後へ      Topへ      次へ

自動テスト: 全般の解説

Rails は自動テストの環境が充実しており、
CI (Continuous Integration) に向いています。

rails generate の多くでテスト用のテンプレートが生成されます。
テンプレートの作法に則ってテストを書いていくとラクです。

通常の開発であれば、
コード変更とテストコード作成はセットです。
コードを書いたら即テストして、デバッグ。
自動テストでほぼカバーできる Rails は、
デグレード・チェックも含めて時間効率が優れています。

が、
サンプルプログラムはコードの一例として書いているだけで、
テストコードの実体は殆どありません。
ご容赦ください。

 

fixtures

\test\fixtures には、
テストで使用する、各モデル毎のデフォルト値を YAML で書きます。

例えば \test\fixtures\books.yml で、

one:
  enabled: true
  name: こころ
  isbn: "978-4101010014"
  category: one
  step: one
  memo: MyString

のようにです。
各テスト実施毎にデータベースがこのデフォルト値になります。

\app\models\book.rb で
belongs_to :category
と定義しているので、
YAML 上の references のカラムは、
ID の数値を直書きせずとも、参照先の YAML のラベルを書けばOKです。
category: one のように。

\test\fixtures\categories.yml のラベル one の ID を自動で入ります。
カラム名自体は category_id ですが、references 同様の書き方なのに留意してください。

実際のテストで使用する際は、
@book = books(:one)
のように、テーブル名(YAMLのラベル) を指定します。

中級

YAML の定義で、ID のカラムに即値を書いても構いませんが、
書かなければ(デフォルトそうですが)、データベース登録時に適当な値が割り当てられます。

また、
通常、全てのテストで全ての YAML がデータベースに適用されます。
これは、\test\test_helper.rbfixtures :all と書いているからです。
通常はこれで支障ないですが、
絞り込みたい場合は、
fixtures :all 部分をコメントアウトした上で、テストファイル毎に設定してください。

e.g.
●\test\test_helper.rb

  module ActiveSupport
    class TestCase
      #fixtures :all

●\test\controllers\books_controller_test.rb

  class BooksControllerTest < ActionDispatch::IntegrationTest
    fixtures :books, :users

こうする場合、全てのテストファイルで fixtures を設定しないといけません。
fixtures :all を活かしたまま各ファイルに fixtures を書いても、
各ファイルの設定で上書きされるわけでは無いようです。

テストの種類

テストは、以下に分類されます。

後、Pundit を使う場合は、policies も追加されます。

中級

大昔(Rails 5.0 まで?) のコントローラテストは、
class BooksControllerTest < ActionController::TestCase
のように、機能テストを継承していました。
が、現在は、
BooksControllerTest < ActionDispatch::IntegrationTest
のように、統合テストを継承しています。
つまり、controllers も integration も仕様的には全く同じですが、
用途によって人間が使い分けるということです。
参考: https://api.rubyonrails.org/classes/ActionController/TestCase.html

parallelize

デフォルトでは、テストを複数のスレッドで動作させるように
\test\test_helper.rb で、
parallelize(workers: :number_of_processors, with: :threads)
と書かれています。

が、エラーになる事が多いので、
parallelize(workers: 1)
にしておくのが安全でしょう。

デフォルトでは、
RuntimeError: Could not find a valid mapping for #<User id: ・・・
ActiveRecord::ActiveRecordError: Cannot expire connection, it is owned by a different thread: #<Thread: ・・・
みたいなエラーになる事があります。
複数スレッド間での衝突が起こっていると思われます。

 

setup、teardown

1つのテストファイル中に複数のテストを実装すると思います。
ファイル内共通で使える以下のメソッドが便利です。

 

e.g.
class BooksControllerTest < ActionDispatch::IntegrationTest
  setup do
    @book = books(:one)
    @user = users(:admin)
    sign_in @user
  end

  teardown do
    # 必要なら書く。
  end

 

テストコード本体

テストコード本体は、以下の形式です。

test "テスト名の文字列" do
  (テストコード)
end

テスト以外のメソッド名の先頭に “test_” は使えません。
例えば、

def test_sub(params)
  # テスト以外のコード
end

などと書くと、このメソッドもテストとして実行されてしまいます。

rails test

ruby bin/rails test
で、System Test 以外 のテストが実行されます。

エラーなど無ければ、
93 runs, 144 assertions, 0 failures, 0 errors, 0 skips
のように表示されて終了します。
エラーが発生したら、その時点でテストを中断し、
エラー内容を表示するので、デバッグします。

エラーメッセージの例:

Failure:
BooksControllerTest#test_should_get_index [test/controllers/books_controller_test.rb:13]:
Expected response to be a <422: unprocessable_entity>, but was a <200: OK>

解説:テストコードでは response が unprocessable_entity になると書いているが、実際には OK だった。

 

部分テスト

指定範囲だけをテストする事も可能です。


後へ      Topへ      次へ