キャッシュストアのInstrumentationテストを`NotificationAssertions`で簡潔化

rails/rails

activesupport/test/cache/behaviors/cache_store_behavior.rb 内の2つのInstrumentationテストが、ActiveSupport::Testing::NotificationAssertionscapture_notifications ヘルパーを使う形に書き換えられました。これにより、手動のサブスクライバー管理と ensure ブロックによるクリーンアップが不要になります。

背景

ActiveSupport::Testing::NotificationAssertions#53065#54126 で導入されたテストヘルパーモジュールです。従来、ActiveSupport::Notifications のイベント発火を検証するには、Notifications.subscribe で手動サブスクライバーを登録し、テスト終了後に ensure ブロックで Notifications.unsubscribe を呼ぶパターンが必要でした。

#53824 でこのヘルパーへの移行が activesupport テストの一部で始まり、今回の #56974 はそのフォローアップとして cache_store_behavior.rb の残余テストを対象にしています。同時期に actionpack 向けの #56956activerecord 向けの #56975 も進行しており、Rails全体でこのヘルパーへの統一が段階的に進められています。

cache_store_behavior.rb はbehaviorモジュールとして、file_store_test.rbmem_cache_store_test.rbmemory_store_test.rbredis_cache_store_test.rb にincludeされるため、ここでの変更は複数のキャッシュストア実装のテスト全体に波及します。

技術的な変更

test_cache_hit_instrumentationtest_cache_miss_instrumentation の2メソッドが、capture_notifications を使う形にリファクタリングされました。変更の本質は、イベントキャプチャのスコープをブロックで明示的に囲む点にあります。

変更前(test_cache_hit_instrumentation):

def test_cache_hit_instrumentation
  key = "test_key"
  @events = []
  ActiveSupport::Notifications.subscribe("cache_read.active_support") { |event| @events << event }
  assert @cache.write(key, "1", raw: true)
  assert @cache.fetch(key, raw: true) { }
  assert_equal 1, @events.length
  assert_equal "cache_read.active_support", @events[0].name
  assert_equal :fetch, @events[0].payload[:super_operation]
  assert @events[0].payload[:hit]
ensure
  ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
end

変更後(test_cache_hit_instrumentation):

def test_cache_hit_instrumentation
  key = "test_key"

  assert @cache.write(key, "1", raw: true)

  events = capture_notifications("cache_read.active_support") do
    assert @cache.fetch(key, raw: true) { }
  end

  assert_equal 1, events.length
  assert_equal "cache_read.active_support", events[0].name
  assert_equal :fetch, events[0].payload[:super_operation]
  assert events[0].payload[:hit]
end

test_cache_miss_instrumentation も同様に書き換えられています。正規表現パターン /^cache_(.*)\.active_support$/ を使ったサブスクライバーも capture_notifications に渡すだけで機能します。

def test_cache_miss_instrumentation
  events = capture_notifications(/^cache_(.*)\. active_support$/) do
    assert_not @cache.fetch(SecureRandom.uuid) { }
  end

  assert_equal 3, events.length
  assert_equal "cache_read.active_support", events[0].name
  assert_equal "cache_generate.active_support", events[1].name
  assert_equal "cache_write.active_support", events[2].name
  assert_equal :fetch, events[0].payload[:super_operation]
  assert_not events[0].payload[:hit]
end

変更量はAdditions・Deletionsともに20行で、アサーション内容は一切変わっていません。インスタンス変数 @events がローカル変数 events に変わり、ensure ブロックが丸ごと削除されています。

設計判断

capture_notifications ブロックでキャプチャスコープを明示する方式 が採用されています。

変更前のパターンでは、@cache.write の呼び出しもサブスクライバー登録後に行われていたため、write 自体が発火するイベントも意図せずキャプチャされる余地がありました。変更後は write をブロック外で先に実行し、fetch のみをブロック内に囲んでいます。これにより、テストが検証したい操作とキャプチャ範囲が一致し、テストの意図がコードの構造から直接読み取れるようになっています。

また、ensure ブロックによるクリーンアップの廃止は、Notifications.unsubscribe の呼び忘れや引数ミスによるテスト間干渉リスクを排除します。capture_notifications がサブスクライバーのライフサイクルをブロックスコープで管理するため、テストの独立性がヘルパー側で保証されます。

まとめ

この変更は、テストの振る舞いを変えずにキャッシュストアのInstrumentationテストを整理したリファクタリングです。capture_notifications によるスコープの明示化と自動クリーンアップの採用により、テストコードの意図が構造から読み取れるようになり、サブスクライバー管理の漏れによる副作用リスクも解消されています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
84a0b26c

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ OK

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

カスタムMarkdown構文 ✓ OK

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

対象読者への適合性 ✓ OK

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

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

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

Diff内容との照合 ✓ OK

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

技術用語の正確性 ✓ OK

技術用語の正確な使用

説明の技術的正確性 ✓ OK

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

事実の突合 ✓ OK

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

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

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

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

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

外部知識の正確性 ✓ OK

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

時間表現の正確性 ✓ OK

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