DEV Community 日本語
フォロー
Spring Data Tests が見逃す可能性のあること:Hibernate の永続化コンテキストを超えたテスト (第7章)
すべてのEntityManagerは、現在の永続化コンテキスト内のエンティティのIDマップである、第一レベルキャッシュを維持します。find()が既に管理されているエンティティに対して呼び出された場合、Hibernateはデータベースに問い合わせることなく既存のオブジェクトを返します。これは標準的なL1キャッシュの動作です。しかし、@DataJpaTestを使用した標準的なSpring Dataテストでは、実際のデータベース永続化ではなく、このL1キャッシュの状態を誤って検証してしまうことがあります。これにより、本番環境になるまで、制約違反の欠落やカラムマッピングのバグといった重大な問題が隠蔽される可能性があります。本稿では、実際のデータベースインタラクションを強制し、それによってテストが真の永続化を検証することを保証するテストアーキテクチャを紹介します。このアーキテクチャは、明示的なEntityManagerのクリア戦略、汎用的なテストフィクスチャ、およびインメモリ分離を採用しています。テスト専用のプロキシが、create()、update()、delete()などのDAO操作をラップし、各書き込み後に自動的にEntityManager.flush()とEntityManager.clear()を呼び出します。これにより、Hibernateは変更をデータベースに書き込み、その後L1キャッシュをクリアすることを強制され、後続の読み取りはキャッシュされたオブジェクトではなく、データベースから直接データを取得することを保証します。このプロキシレイヤーはテストスコープ専用であり、本番コードへのパフォーマンス影響を防ぎます。loadById()やカスタム@Queryメソッドのような読み取り操作については、プロキシは介入しません。これらの操作は、クリア後に新しいデータを取得するか、本質的に実際のSQLを発行するためです。さらに、TablesEraserユーティリティを使用して、各テストの前にすべてのテーブルを空にし、クリーンなスキーマを保証し、テスト間で第二レベルキャッシュの汚染やバッチ処理の副作用を防ぎます。このプロセスでは、DELETE FROMステートメントを使用し、トランザクションを尊重し、安全なロールバックを提供します。AbstractCrudTestCaseやAbstractSearchableTestCaseのような再利用可能な抽象テストクラスは、一般的なCRUDおよび検索機能に対する共有アサーションを提供します。これらの抽象クラスは、構造的に同一のテストケースを処理し、ボイラープレートを削減します。一方、具体的なテストクラスは、ドメイン固有のペイロード生成を実装し、独自のクエリメソッドのテストを追加します。このレイヤードアプローチにより、基底クラスが共通の動作を管理し、特定のDAOが拡張およびカスタマイズする必要がある場合にそれを行うことができます。このシステムの有効性の重要な例として、テストケースであるtestSearchNullParamsがあります。これは、検索操作の境界条件を特別にチェックします。このテストは、nullのParamsオブジェクトが渡された場合に、search()実装の初期バージョンでNullPointerExceptionを明らかにしました。これは、デプロイ前に実際のワールドの問題を検出する設計能力を検証し、堅牢性を確保しました。