Active Recordテストの散発的失敗を修正する3つのバグ修正

rails/rails

Active Recordで発生していた散発的なテスト失敗(フレーキーテスト)を引き起こす3件の問題を修正しました。非同期クエリ統計の誤算、モデルファイルの循環的な依存関係、テストファイルのrequire順序がそれぞれ修正されています。

背景

本PRは、Railsのテスト基盤を megatest へ移行する作業(#57326)から切り出されました。Active ModelとActive Recordをmegatestに移行する過程でフレーキーテストが顕在化し、独立したPRとして先行修正することになりました。megatestへの移行はテスト実行の並列化を含むため、既存の競合状態や依存関係の問題が通常よりも露わになりやすい環境です。

技術的な変更

3つの変更はそれぞれ独立した問題を修正しています。

1. runtime_registry.rb — 非同期クエリ統計の誤算修正

async_sql_runtime の加算処理において、lock_waitnil の場合に 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.rbPersonモデルの明示的な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.rbrequire順序の整列

join_model_test.rb において enginecarrequire 順序が入れ替えられました。変更前は enginecar より先に 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件の修正はいずれも「暗黙の前提」を「明示的な保証」へと改める変更です。テスト並列化という負荷が高い環境への移行を契機にフレーキーテストが顕在化した事例であり、依存関係の宣言とガード条件の追加という基本的な手法でフレーキネスを解消しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
8b6b370a

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論→各論→結論」の構成が明確です。リード文、背景、技術的な変更、設計判断、まとめの各要素が適切に配置されており、記事の流れが非常にスムーズです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトやGitHubのPRへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

内容はRailsの内部実装に関するもので、専門知識を持つエンジニアという対象読者に適切です。過度な説明がなく、簡潔に要点がまとめられています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクション、各パラグラフが「総論→各論」の構造で書かれています。トピックセンテンスが段落の冒頭にあり、非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されている3つのコード変更は、提供されたDiff情報と完全に一致しています。ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「フレーキーテスト」「counter_cache」「競合状態」などの技術用語が、文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

各コード変更の理由と影響に関する説明は、Diffの内容と論理的に整合性が取れており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の内容はすべてPR情報(Description、Diff)に基づいています。「megatest」に関する背景説明も、PRのコンテキストを補足する妥当なもので、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57328, #57326)やファイルパスなどの固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトル「Fix some Active Record flakes」の内容をより具体的に、かつ正確に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に記載のないバージョン情報やリリース予定日などの外部知識の捏造はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「既に」や「将来」といった時間表現の歪曲はなく、事実関係が正確に記述されています。