テストヘルパーにDBクリーンアップブロックを追加し、per-PIDデータベースの残留を防止

rails/rails

use_postgresql / use_mysql2 がブロックを受け取れるようになり、テスト実行後に生成された railties_${Process.pid}_% パターンのデータベースを自動的に削除するようになりました。これにより、並列テストが作成するper-PIDデータベースがテスト環境に残留し続ける問題が解消されます。

背景

Railsのインテグレーションテストでは、ActiveRecord::TestDatabases.create_and_load_schema を通じてper-PIDおよびper-workerのデータベースが動的に生成されます。しかし rails db:dropconfig/database.yml に定義されたデータベースしか削除しないため、railties_${Process.pid}_% という命名パターンで生成されたデータベースはテスト終了後も残留していました。

この問題に対応するため、各テストは ensure 節で個別に rails "db:drop" を呼び出していましたが、この方法では動的に生成されたデータベースは対象外でした。PR本文では、199ケースのテスト実行後に psql -d postgres -At -c 'SELECT datname FROM pg_database' のスナップショットを比較する形で検証が行われ、残留データベースがゼロになることが確認されています。なお、兄弟PR #57457 は同テストで発生する app_development / app_test データベースの残留を別途修正しています。

技術的な変更

中心となる変更は railties/test/isolation/abstract_unit.rb への with_test_database_cleanup ヘルパーの追加です。このヘルパーがブロック実行前後のデータベース一覧を差分比較し、ブロック内で新規作成されたデータベースを ensure で削除します。

追加された3つのメソッドがそれぞれ役割を分担しています:

def with_test_database_cleanup(adapter)
  pre_existing_databases = list_test_databases(adapter) rescue nil
  yield
ensure
  if pre_existing_databases
    drop_test_databases(adapter, list_test_databases(adapter) - pre_existing_databases)
  end
end

def list_test_databases(adapter)
  saved_db_config = ActiveRecord::Base.connection_pool.db_config rescue nil
  ActiveRecord::Base.establish_connection(database_maintenance_config_for(adapter))
  ActiveRecord::Base.lease_connection.select_values(database_list_sql_for(adapter))
ensure
  ActiveRecord::Base.remove_connection
  ActiveRecord::Base.establish_connection(saved_db_config) if saved_db_config
end

def drop_test_databases(adapter, databases)
  saved_db_config = ActiveRecord::Base.connection_pool.db_config rescue nil
  config = database_maintenance_config_for(adapter)
  quietly do
    databases.each do |db|
      ActiveRecord::Tasks::DatabaseTasks.drop(config.merge(database: db))
    end
  end
ensure
  # ...
end

list_test_databases は実行前後の接続状態を saved_db_config で退避・復元するため、テスト自体のDB接続に影響を与えません。drop_test_databasesActiveRecord::Tasks::DatabaseTasks.drop を利用しており、rails db:drop コマンドではなくActiveRecordのタスクAPIを直接呼び出すことで、config/database.yml の定義に縛られずに任意のデータベースを削除できます。

既存の呼び出し箇所はブロック構文に移行されています。たとえば test_runner_test.rb の変更は典型的なパターンです:

変更前:

use_mysql2

exercise_parallelization_regardless_of_machine_core_count(with: :processes)
rails "generate", "scaffold", "User", "name:string"
# ...

変更後:

use_mysql2 do
  exercise_parallelization_regardless_of_machine_core_count(with: :processes)
  rails "generate", "scaffold", "User", "name:string"
  # ...
end

各テストに散在していた ensure rails "db:drop" rescue nil 節は削除され、クリーンアップロジックがヘルパーに集約されました。また runner_test.rb では use_postgresql がブロック引数として db_name を渡すようになっており、ブロック外の変数への依存も解消されています。

設計判断

スナップショット差分方式(「ブロック実行前のDB一覧」と「実行後のDB一覧」の差分を削除)が採用されました。これにより、ヘルパー自身がどのデータベースが生成されたかを追跡する必要がなく、テスト内部の実装詳細に依存せずクリーンアップを実現できます。

list_test_databases の呼び出しが rescue nil でガードされている点も注目に値します。メンテナンス用接続の確立に失敗した場合(例えばDBサーバーが起動していないCI環境など)、クリーンアップを試みず pre_existing_databasesnil のままにすることで、ensure 節でのクリーンアップ処理をスキップします。これにより、クリーンアップ自体の失敗がテスト結果に影響しない設計になっています。

Rails::Command::DevcontainerTestuse_mysql2 呼び出しは config/database.yml の書き込みのみでデータベースを実際には作成しないため、今回のブロック化の対象外となっています。

まとめ

この変更は、テストヘルパーにスナップショット差分ベースのクリーンアップ機構を導入することで、config/database.yml の管理外にある動的生成データベースの残留問題を根本から解決しています。クリーンアップロジックの一元化と rescue nil によるフェイルセーフな設計は、テストインフラの堅牢性向上と各テストコードの簡素化を同時に実現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
02947055

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→セクション群(各論)→まとめ(結論)の構成が明確であり、必須要素(背景、技術的変更)と任意要素(設計判断)が適切に配置されています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:filepath)とGitHubのPRリンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

専門用語が適切に使用され、過度な説明がないため、専門知識を持つエンジニアという対象読者に適合しています。

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

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

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、高い可読性を確保しています。

Diff内容との照合 ⚠ WARNING

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

`drop_test_databases`メソッドのコード引用において`ensure`節の中身が省略されていますが、説明の主旨を損なうものではなく、技術的な理解を妨げません。他のコード引用は正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`per-PIDデータベース`, `ActiveRecord::Tasks::DatabaseTasks.drop`などの技術用語が、PR情報や文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「`config/database.yml`に縛られずにDBを削除できる」といった説明は、Diff内のコードによって裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内の主張はすべてPRのDescription、Diff、Additional informationで裏付けられており、ハルシネーション(創作された事実)は検出されませんでした。

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

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

PR番号(#57458)、兄弟PR番号(#57457)などの数値や固有名詞は正確に記載されています。

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

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

記事タイトルはPRの主題「テストヘルパーにブロックを渡し、テスト用DBをクリーンアップする」を正確に要約しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

バージョンサポート状況やリリース日程など、PR情報にない外部知識の追加はなく、記事内容は提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

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