`activerecord`テストの`NotificationAssertions`ヘルパーへの移行
activerecordの11件のテストがActiveSupport::Testing::NotificationAssertionsヘルパーを使用するよう簡素化されました。手動でのサブスクライバ管理やensureブロックによるクリーンアップが不要になり、テストコードの可読性が向上しています。
背景
ActiveSupport::Notificationsを使ったテストには、これまで定型的なボイラープレートコードが必要でした。#53065でActiveSupport::Testing::NotificationAssertionsモジュールが追加され、#54126でペイロードのサブセットマッチングと通知の返却に対応したことで、これらの定型コードを排除できる基盤が整いました。
今回のPRは、#53822に続くactiverecordテストの第2弾クリーンアップです。#56956(actionpack)や#56974(activesupport)と並行して進められており、Rails全体でテストコードの統一的な書き直しが行われています。
従来のパターンは、コールバックの定義・購読・コードブロックの実行・購読解除という4つのステップを手動で管理する必要があり、ensureブロックによるクリーンアップが漏れるリスクもありました。
技術的な変更
capture_notifications ヘルパーへの置き換えにより、購読のライフサイクル管理がブロックスコープに封じ込められ、コード量が大幅に削減されています。
empty_all_tables_test.rbの変更は、置き換えパターンを端的に示しています。変更前は購読・実行・購読解除を個別に記述していましたが、変更後は1つのメソッドチェーンに集約されています。
変更前:
queries = []
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |event|
queries << event.payload[:sql] if event.payload[:name] == "Delete Tables"
end
@conn.empty_all_tables
ActiveSupport::Notifications.unsubscribe(subscriber)
変更後:
queries = capture_notifications("sql.active_record") { @conn.empty_all_tables }
.select { |e| e.payload[:name] == "Delete Tables" }.map { |e| e.payload[:sql] }
associations/deprecation_test.rbでは、ペイロードへのアクセス方法が変化しています。変更前はpayloads << event.payloadでペイロードのHashを収集していたためpayloads[0][:reflection]と参照していましたが、変更後はcapture_notificationsがイベントオブジェクト自体を返すためevents[0].payload[:reflection]と参照します。
transaction_isolation_test.rbでは、ActiveSupport::Notifications.subscribedに渡していた複数行のlambdaと終端のendブロックが、capture_notificationsブロック+末尾の.map { |e| e.payload[:sql] }に置き換わり、3つのテストでそれぞれ約8行が削減されています。
フィルタリングも統一されており、SCHEMAイベントの除外は.reject { |e| e.payload[:name] == "SCHEMA" }、SQLテキストの抽出は.map { |e| e.payload[:sql] }と、いずれもブロックの戻り値に対するメソッドチェーンとして表現されています。
設計判断
capture_notificationsはイベントオブジェクトのArrayを返す設計になっており、フィルタリングや変換は呼び出し側がメソッドチェーンで行う方針が採られています。
この設計は、ヘルパー側にフィルタ条件を組み込まず、Rubyの標準的なEnumerableメソッドに委ねることで、APIを最小限に保っています。購読の開始と終了はヘルパーが自動管理し、収集したイベントの加工はRubyのイディオムに任せるという明確な責務分担です。
assert_notificationを使わずcapture_notificationsのみを使う形を選択しているケースもありますが、これはPR本文が「直接アサーションできる場合はヘルパーを使い、そうでない場合はcapture_notificationsでフォールバックする」と説明しているとおり、柔軟な使い分けが意図されています。
まとめ
本PRは機能変更を含まないテストコードのリファクタリングですが、ActiveSupport::Notificationsの購読管理という横断的な関心事をヘルパーに集約することで、テストの意図が明確になっています。購読解除の漏れというクラスのバグリスクを構造的に排除しつつ、既存のEnumerableメソッドとの自然な連携を維持した設計といえます。