SQLite3 メモリ DB での順序依存テスト失敗を防ぐ修正

rails/rails

Rails の Active Record テストスイートを SQLite3 のインメモリデータベースで実行すると、テスト実行順に依存した数千件の失敗が発生していました。本 PR は、SchemaCache の破壊とデータベース破棄という二つの根本原因を除去し、テストの安定性を回復します。

背景

テストの順序依存は、ARCONN=sqlite3_mem bundle exec rake sqlite3_mem:test を走らせた際に顕在化し、特定のテストが実行された後に以降のテストが「テーブルが見つからない」エラーや属性欠損で失敗する状態でした。原因は主に二点に集約されます。

  1. DirtyTest#test_field_named_fieldActiveRecord::Base.clear_cache! を呼び出し、グローバル SchemaCache を空のインスタンスに置き換えていたため、続くテストはテーブル情報を取得できず例外を投げました。
  2. AdapterConnectionTest の一部 sql_notifications テストが run_without_connection を使用した際、SQLite の :memory: データベースでは remove_connection がメモリ DB を破壊し、再接続後にスキーマが失われていました。

この二つの問題が組み合わさり、テスト全体が不安定になるという事象が発生していました。

技術的な変更

SchemaCache の汚染を防ぐ修正(dirty_test.rb

test_field_named_field はテーブルを一時的に作成・削除し、ensure 句で clear_cache! を呼んでいました。この実装を次のように置き換え、SchemaCache への直接的なクリア操作を排除しました。

-    class Testings < ActiveRecord::Base; end
-    def test_field_named_field
-      ActiveRecord::Base.lease_connection.create_table :testings do |t|
-        t.string :field
-      end
-    ensure
-      ActiveRecord::Base.lease_connection.drop_table :testings rescue nil
-      ActiveRecord::Base.clear_cache!
-    end
+    def test_field_named_field
+      klass = Class.new(ActiveRecord::Base) do
+        self.table_name = "pirates"
+        attribute :field, :string
+      end
+      assert_nothing_raised { klass.new.attributes }
+    end

変更点は
* 一時テーブル testings の生成・削除を廃止し、既存の pirates テーブルを再利用。
* attribute API を使用した匿名クラスで属性を定義。
* clear_cache! 呼び出しを除去し、グローバルキャッシュの破壊を防止。

メモリ DB での接続破壊を回避(adapter_test.rb

AdapterConnectionTestsql_notifications 系テストを unless in_memory_db? ガードで囲み、インメモリ環境では実行しないようにしました。また、テスト内部で通知キャプチャを行うロジックを明示的に記述しています。

-    test "suppresses notifications when sql_notifications=false" do
-      run_without_connection do |orig_connection|
-        ActiveRecord::Base.establish_connection(orig_connection.merge(sql_notifications: false))
-        notifications = capture_notifications("sql.active_record") { Post.first }
-        assert_empty notifications
-      end
-    end
-
-    test "sql_notifications are enabled by default" do
-      notifications = capture_notifications("sql.active_record") { Post.first }
-      assert_not_empty notifications
-    end
+    unless in_memory_db?
+      test "suppresses notifications when sql_notifications=false" do
+        run_without_connection do |orig_connection|
+          ActiveRecord::Base.establish_connection(orig_connection.merge(sql_notifications: false))
+          notifications = capture_notifications("sql.active_record") { Post.first }
+          assert_empty notifications
+        end
+      end
+
+      test "sql_notifications are enabled by default" do
+        notifications = capture_notifications("sql.active_record") { Post.first }
+        assert_not_empty notifications
+      end
+    end

主なポイントは
* in_memory_db? ヘルパーでインメモリかどうか判定。
* ガード内にテスト全体を移動し、run_without_connection がメモリ DB を破壊するケースを除外。
* 期待する通知の有無を明示的にアサート。

設計判断

この PR では テストコードレベルでの防御 を選択し、コアライブラリの振る舞いを変更せずに問題を解消しました。具体的には
* グローバル状態(SchemaCache)への直接的なクリア をやめ、代わりに既存テーブルと属性 API を活用することでテストの自己完結性を確保。
* インメモリ DB の特性 を判定する in_memory_db? ガードを導入し、破壊的な接続操作を回避。これにより、他のテストや本番コードへの影響を最小限に抑えています。
* 変更は テストファイルのみ に留まり、アプリケーションロジックや設定に副作用を与えない設計です。結果として、既存テストの実行順序に依存しない安定したテスト環境が提供されます。

まとめ

本修正は、SQLite3 のインメモリデータベース使用時に発生していた SchemaCache の汚染接続破壊 という二つの根本原因をテストコード側で切り離すことで、数千件に及ぶ順序依存テスト失敗を根絶しました。テストの信頼性が向上したことで、ローカルや CI 環境でのフィードバックが一貫し、今後の機能追加やバグ修正の検証コストが低減します。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
ed8ad4d5

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文・背景・技術的変更・設計判断・まとめの5セクションが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ✓ PASS

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

コードブロックは `言語:ファイルパス` 形式で正しく記載され、PRリンクは #番号付きでリンク化されています。

対象読者への適合性 ✓ PASS

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

Rails のテストコードに関する高度な内容で、初心者向けの過剰な解説はありません。

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

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

各セクションは総論・各論・結論の段落構成になっており、トピックセンテンスで始まり、段落は適切な長さで空行で区切られています。

Diff内容との照合 ⚠ WARNING

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

`dirty_test.rb` の変更で、`assert_nothing_raised { klass.new.attributes }` と記載されていますが、Diff では `do...end` ブロックが使用されています。内容は同等ですが、Diff に正確に存在しないコードです。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

SchemaCache、in_memory_db?、run_without_connection など、PR で使用されている用語が正しく使われています。

説明の技術的正確性 ✓ PASS

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

PR の背景・目的・修正内容と記事の説明が一致しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事の主張はすべて PR の記述や Diff に裏付けられており、捏造や憶測はありません。

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

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

PR 番号 #57590、約3000件(記事では「数千件」)は PR の記載と矛盾しません。

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

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

記事タイトルは PR タイトル「Fix order-dependent test failures with `sqlite3_mem`」の内容を適切に日本語化しています。

外部知識の正確性 ✓ PASS

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

LTS、リリース日程等の外部情報は一切含まれておらず、PR に限定した内容です。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PR の記述と一致しています。