Active Recordテストの散発的失敗を修正する3つのバグ修正
Active Recordで発生していた散発的なテスト失敗(フレーキーテスト)を引き起こす3件の問題を修正しました。非同期クエリ統計の誤算、モデルファイルの循環的な依存関係、テストファイルのrequire順序がそれぞれ修正されています。
背景
本PRは、Railsのテスト基盤を megatest へ移行する作業(#57326)から切り出されました。Active ModelとActive Recordをmegatestに移行する過程でフレーキーテストが顕在化し、独立したPRとして先行修正することになりました。megatestへの移行はテスト実行の並列化を含むため、既存の競合状態や依存関係の問題が通常よりも露わになりやすい環境です。
技術的な変更
3つの変更はそれぞれ独立した問題を修正しています。
1. runtime_registry.rb — 非同期クエリ統計の誤算修正
async_sql_runtime の加算処理において、lock_wait が nil の場合に runtime - nil という演算が発生し NoMethodError を引き起こす可能性がありました。変更前は async フラグのみをチェックしていたため、lock_wait が渡されないケースで計算が失敗していました。
変更前:
if async
stats.async_sql_runtime += (runtime - lock_wait)
end
stats.sql_runtime += runtime
変更後:
if async && lock_wait
stats.async_sql_runtime += (runtime - lock_wait)
end
stats.sql_runtime += runtime
lock_wait の存在を条件に加えることで、nil に対する算術演算を防いでいます。また、stats.sql_runtime += runtime の前に空行が追加され、非同期ブロックとの視覚的な分離も改善されています。
2. car.rb — Personモデルの明示的なrequire追加
Car モデルは belongs_to :person, counter_cache: true を宣言しているにも関わらず、Person モデルの require が欠落していました。テスト実行順序によっては Person が未ロードのままとなり、counter_cache の解決に失敗するフレーキーテストの原因となっていました。
変更後:
# frozen_string_literal: true
require_relative "person"
class Car < ActiveRecord::Base
belongs_to :person, counter_cache: true
has_many :bulbs
require_relative "person" を明示することで、テストのロード順序に依存しない確実な依存解決が保証されます。
3. join_model_test.rb — require順序の整列
join_model_test.rb において engine と car の require 順序が入れ替えられました。変更前は engine が car より先に require されていましたが、Car モデルが Person への依存を持ちながらそれを宣言していないため、engine のロード時に Car が間接的に参照されると Person が未ロードのまま処理が進む恐れがありました。car を先に require する順序に変更することで、変更2で追加した require_relative "person" が確実に先に実行されるようになります。
変更前:
require "models/engine"
require "models/car"
変更後:
require "models/car"
require "models/engine"
この変更は car.rb 側への require_relative "person" 追加とセットで機能しています。car を先に require することで Person のロードが保証され、その後 Engine が安全にロードされる順序が確立されます。
設計判断
依存関係の明示化をモデルファイル側で行う方針が採用されました。テスト側の require 順序を変えるだけでなく、car.rb 自身が person への依存を require_relative で宣言することで、どのテストファイルからロードされても正しく動作するよう修正されています。テストのロード順序に依存した暗黙の解決に頼らず、依存関係をモデル定義と同じファイルに記述する設計は、テスト分離の観点から堅牢です。
runtime_registry.rb の修正も同様に、引数の存在を前提とせず防御的な条件分岐を追加する方向性です。lock_wait がオプショナルであるという事実を条件式に明示することで、呼び出し側の実装ミスによる統計誤計算を防いでいます。
まとめ
本PRの3件の修正はいずれも「暗黙の前提」を「明示的な保証」へと改める変更です。テスト並列化という負荷が高い環境への移行を契機にフレーキーテストが顕在化した事例であり、依存関係の宣言とガード条件の追加という基本的な手法でフレーキネスを解消しています。