非トランザクションテスト後のフィクスチャキャッシュを適切にリセットする
ActiveRecordのテストフィクスチャにおいて、非トランザクションテスト実行後にActiveRecord::FixtureSet.reset_cacheが呼ばれないバグが修正されました。これにより、トランザクションテストと非トランザクションテストが混在する環境での実行順序に依存したフィクスチャの不整合が解消されます。
背景
この修正は、#57326(RailsのテストスイートをMinitest風のスイート単位ではなくテスト全体でランダム順に実行するmegatest移行)から抽出されたものです。megatestは、テストの実行順をスイート単位ではなく完全にランダム化するため、トランザクションフィクスチャを無効にしたテスト(非トランザクションテスト)と通常のトランザクションテストが交互に実行されるケースが大幅に増加します。
Minitestの従来の実行モデルでは、同じテストクラス内のテストが連続して実行されるため、非トランザクションテストの後に別クラスのトランザクションテストが続くことは稀でした。しかしmegatestの完全ランダム実行ではこの前提が崩れ、フィクスチャキャッシュの状態管理が問題になりました。
技術的な変更
問題の核心は、ActiveRecord::FixtureSet.reset_cacheの呼び出し位置が誤っていたことです。このメソッドはフィクスチャのファイルパース結果などのキャッシュをクリアしますが、従来は非トランザクションテストのsetup_fixtures内でのみ呼ばれており、invalidate_already_loaded_fixturesメソッドには含まれていませんでした。
変更前:
if run_in_transaction?
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
unless @loaded_fixtures
@@already_loaded_fixtures.clear
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
end
setup_transactional_fixtures
else
# Load fixtures for every test.
ActiveRecord::FixtureSet.reset_cache
invalidate_already_loaded_fixtures
@loaded_fixtures = load_fixtures(config)
end
変更後:
if run_in_transaction?
unless (@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key])
invalidate_already_loaded_fixtures
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
end
setup_transactional_fixtures
else
# Load fixtures for every test.
invalidate_already_loaded_fixtures
@loaded_fixtures = load_fixtures(config)
end
# ...
def invalidate_already_loaded_fixtures
ActiveRecord::FixtureSet.reset_cache
@@already_loaded_fixtures.clear
end
ActiveRecord::FixtureSet.reset_cacheをinvalidate_already_loaded_fixturesメソッド内に移動することで、非トランザクションテストが実行された後にトランザクションテストが続く場合でも、@@already_loaded_fixturesクリアと同時にフィクスチャのファイルキャッシュもリセットされるようになりました。また、トランザクションテストのキャッシュミス時にもinvalidate_already_loaded_fixturesを呼び出すよう変更され、両コードパスで一貫したキャッシュ管理が行われます。
付随して、fixtures_test.rb内の2つのテストケースで、ActiveRecord::TestCaseを継承していたクラスがActiveSupport::TestCaseにActiveRecord::TestFixturesをincludeする形式へ変更されています。これにより、テスト対象のコードパスがActiveRecord::TestCaseの追加の前提に依存しなくなり、ActiveRecord::TestFixturesモジュールそのものの動作を直接検証できます。
設計判断
invalidate_already_loaded_fixturesを単一の責務を持つメソッドに整理するアプローチが採用されています。変更前はFixtureSet.reset_cacheと@@already_loaded_fixtures.clearの2つの操作が別々の場所に分散しており、「フィクスチャの無効化」が不完全にしか実行されないパスが存在していました。
この修正では「フィクスチャキャッシュの無効化」という概念をinvalidate_already_loaded_fixturesメソッドに集約し、2つのキャッシュ層(FixtureSetのファイルパースキャッシュと@@already_loaded_fixturesのロード済みフィクスチャキャッシュ)を常にセットで操作することを保証しています。呼び出し側がどちらかの操作を忘れるという人的ミスを構造的に防いでいます。
また、tools/support/leak_checker.rbへの変更も含まれています。@__leak_checker_before_envが設定されていない場合にafter_teardownが早期リターンするガード節が追加されており、before_setupが呼ばれていないテストでのnil参照を防いでいます。
まとめ
本PRは、2つのキャッシュ層の無効化操作をinvalidate_already_loaded_fixturesに集約することで、テスト実行順序に関わらずフィクスチャキャッシュの整合性を保証します。megatestへの移行という文脈で顕在化したバグですが、本質的にはMinitest環境でも実行順次第で同様の問題が起こりうる設計上の欠陥の修正であり、フィクスチャの信頼性向上に貢献します。