RailsのMinitestを使用していて、github actionを使用してCIを導入しようとしているのですが、その際に苦戦した話と対応方法を記載しておこうと思います。
前提
・1つのデータベースではなく、複数のデータベースを取り扱っている
・データベース単位かつテスト単位でfixtureを設定している
・setupの中でfixtureを設定している
テスト実行後にDBに値が残り、それが他の自動テストのアサートに影響を与えてしまう
minitestを使用している時、基本的にはsetupの中でfixtureを設定しているようにしているのですが、テスト内で異なるDB同士でテーブルをjoinしているケースの場合にトランザクション範囲の関係で、fixtureで設定したデータが主テーブルに関しては引けるが、子テーブルでは引けないという事態になっていました。それを避ける為、runメソッドやuse_transactional_testsなどのオプションを使用することでトランザクションを無効にする対応を取っていました。ただこうしたケースの場合、transactionが効かない状態なのでロールバックされず、テスト実行後もデータが残り続けてしまいます。そしてデータが残ってしまうと、その他のテストでレコードが存在しないはずなのに取れてしまい、アサートがズレてしまうという現象が起きるわけです。
対策方法(teardownを活用しよう)
考えた対策として、teardownの中でデータ削除の処理を行うようにすることにしました。teardownは各テスト実行後に呼ばれるメソッドで、タイミングとしてここに記載するのが適切だと考えました。
setup do
ActiveRecord::FixtureSet.reset_cache
fixture_directory = Rails.root.join('test', 'fixtures', 'customer_controller_test')
fixture_files = [
:customer_products
]
@fixture_models_customer_product = {
customer_products: CustomerProducts
}
ActiveRecord::FixtureSet.create_fixtures(fixture_directory, fixture_files, @fixture_models_customer_product, CustomerProductsDb)
fixture_files = [
:customer_info
]
@fixture_models_customer_info = {
customer_info: CustomerInfo
}
ActiveRecord::FixtureSet.create_fixtures(fixture_directory, fixture_files, @fixture_models_customer_info, CustomerInfoDb)
end
teardown do
delete_database
end
teardownに記載しているdelete_databaseですが、これはtest_helperに定義しているメソッドです。ここではポイントとして、モデル情報を定義するハッシュ値をインスタンス変数にしている点です。こうすることでtest_helper内からもこの値を参照することができます。
def delete_database
if @fixture_models_customer_product.present?
CustomerProductsDb.connection.execute "SET FOREIGN_KEY_CHECKS = 0;"
@fixture_models_customer_product.values.each { |model| model.delete_all }
CustomerProductsDb.connection.execute "SET FOREIGN_KEY_CHECKS = 1;"
elsif @fixture_models_customer_info.present?
CustomerInfoDb.connection.execute "SET FOREIGN_KEY_CHECKS = 0;"
@fixture_models_customer_info.values.each { |model| model.delete_all }
CustomerInfoDb.connection.execute "SET FOREIGN_KEY_CHECKS = 1;"
end
end
あらかじめDB単位でここのif分に記載しておけば、運用としてrunメソッドやuse_transactional_testsを用いているテストは、teardownでこの関数を呼びさえすれば勝手にデータを削除をしてくれるような仕組みにしています。データを削除する際に外部キー制約を貼っている場合、その子テーブルを先に削除しろと怒られるので、SET FOREIGN_KEY_CHECKS = 0;で一時的に外部キー制約を無効にし、データを削除した後その外部キー制約を元に戻すようにしています。先頭のconnection.executeはActiveRecord::Baseクラスが保持しているSQLの実行を行うメソッドで、connection.executeの後にクエリを記載することでそのクエリを実行してくれます。
終わりに
CIを導入するつもりが、思わぬ伏兵に出くわして本来やろうとしてたことから大きくその範囲が広がってしまった印象です。同じようなケースで悩んでいる方がいらっしゃれば参考にして頂ければと思います。