概要
プログラムを作成すると多くの場合はテストコードを作成すると思います
Railsでテストを実行しているときに「Userデータ作成、作成したらXXも合わせて作成される」みたいなケースがあったときに開発者はあまり考えることはせずともテスト実行後に作成したデータが消えていることが多いと思います
Railsには標準でその仕組が存在し見ていきます
また標準ではなくDataBaseCleaner
やDatabaseRewinder
といったGemを利用する選択もあるので、Gemを利用するケースについて考えてみます
名前 | バージョン |
---|---|
Rails | 7.2.0-alpha |
rspec-rails | 6.2.0.pre |
※ バージョンは2023/12/13の各Gemのmainブランチ
テストデータの自動ロールバックについて
結論: テスト実行時にトランザクションを開始し、テストケースが終了する毎にトランザクションへロールバックしてデータを戻します
詳細な実装は ActiveRecord::TestFixtures
にあります
Railsガイドに書いてあるとおり、TestFixtures
内にuse_transactional_tests
を利用したロジックが存在し、それによってテスト実行前(before_setup
メソッド内)にトランザクションを開始し、テスト実行後(after_teardown
メソッド内)でトランザクションのロールバックを行いデータを巻き戻してます
before_setup
とafter_teardown
がテスト前後のコールバックで呼び出される実態はActiveSupport::Testing::SetupAndTeardown
に存在します
基本的にはGemを導入したり、自前でデータ削除を行わなくてもこの仕組みによってテストデータをクリーンに保てます
Gemを利用するケース
そこを考えるとGemを利用するケースとしてはトランザクションでデータを作成・削除が行えないケースになると思います
たとえばRailsガイドでは「並列トランザクションをテストする」という項目でテストケースのクラスでトランザクションを無効にする例を提示しています
並列トランザクションをスレッドで実行するコードをテストしたい場合、テスト用トランザクションの下にすでにネストされているため、トランザクションが互いにブロックしてしまう可能性があります。
self.use_transactional_tests = falseを設定すると、テストケースのクラスでトランザクションを無効にできます。
またテスト実行中(テスト対象のコード)でコネクションを接続する処理を記述した場合はトランザクションが新しく開始され、コネクション接続後に接続前に作成したデータの参照がうまくできなくなります
describe do
before do
Content.create(title: 'hoge', name: 'fuga')
Content.establish_connection(:default)
Content.create(title: 'hoge', name: 'fuga')
end
it do
expect(Content.count).to eq 2 # error Content.countは1で返ってくる
end
end
これもRailsの標準のデータを戻す手段であるトランザクションを利用しているために発生するので、トランザクションを無効にし、Gemを利用してデータを削除するとよいでしょう
次にRailsのテストフレームワークとしては use_transactional_tests
のパラメーターをfalseにすることでトランザクションでクリーンにするのをやめていますが、RSpecの場合を見てみます
RSpecだとどうやってトランザクションを管理をやめるか
RSpecの場合はuse_transactional_fixtures
という名前で同様の設定ができます
具体的にはspec/rails_helper.rb
もしくはspec/spec_helper.rb
内のRspec.configuration.use_transactional_fixtures
です
Rspec.configuration do |config|
config.use_transactional_fixtures = true # or false
end
use_transactional_fixtures
に設定した値がActiveRecord::TestFixtures.use_transactional_tests
を上書きしてるのでそのようなことができます
この記述はRSpec::Rails::FixtureSupport
に存在します
module RSpec
module Rails
module FixtureSupport
if defined?(ActiveRecord::TestFixtures)
extend ActiveSupport::Concern
....
include ActiveRecord::TestFixtures
....
included do
....
self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
終わりに
最近接続の切り替えを手動で行う事を多く調べ、その際にテストで詰まっていたので調べた内容をまとめました
コネクション接続を明示的に指定し、テストがうまく通らなくなったなどのケースが発生したときに参考になれば幸いです