テスト設計というのは非常に難しいです。
ちょっとでも見誤ると、すぐにテストができない設計になってしまい、テストをしないという設計になってしまいがちです。
これを避けるためにTDDを行うのですが、粒度の違うUTとE2Eではテストの設計が違ってくるのでやっぱり難しい。
今回の記事では、テスト設計について語るわけではありません。
テストの難易度が高いので、少しでも難易度を下げられる手段をとれるようにする、というのが今回の記事の目標です。
環境
- Java
- Spring
- TestExecutionListener
今回の記事でしない話
- カスタムバリデーションのための自作アノテーション
- どこかで検証して、記事にする予定。
想定しているユースケース
- DBのレコードの削除
- シーケンスの初期化
- 指定していない場合はすべてリセット
- マスタをメモリに読み込ませて高速でリセットさせる
- 指定していない場合はすべてリセット
テーブルをセットアップするアノテーションはライブラリで用意されています。
// テスト前のセットアップ(CLEAN_INSERT指定することが多い。) @DatabaseSetup(value = "/test/hogeApi/A01/setup.xml")
ですが、目的のテーブルだけ削除する・特定のシーケンスを初期化する、等のアノテーションは用意されていません。
シーケンスを初期化しない場合は、対象項目を無視しないと複数のテストを動かしたときにシーケンスがずれてしまい、まともな検証ができなくなります。
かといって、テスト設計の不備で、不必要にテストを緩くするのは良くないです。
もちろん、品質が落ちる前提でのテスト設計であれば、トレードオフなので状況次第では採用もありです。
そこで、自作アノテーションを作ることで、痒いところに手を届くようにしましょう。
処理自体は、メソッドとして用意するのと変わりません。
アノテーションで記載すると、テストメソッドの外に記載するので、テスト自体の可読性が上がります。
…たぶん。
まぁ、自作アノテーションで失敗すると、原因が分かりづらくなることはあります…。
あと、自作アノテーションを作ると、アノテーションに対して理解が深まるので、アノテーションのソースも読めるようになります。
…これも、たぶん。
使い方
自作アノテーションを作る
詳細の作り方は、他の方の記事に任せます。
DBSetupというメソッドで動くアノテーションを作成しています。
@Documented // JavaDoc生成時に出力されるようになる @Retention(RetentionPolicy.RUNTIME) // 実行時に使用する @Target(ElementType.METHOD) // 付与対象を指定。これはメソッド。 public @interface DBSetup { String path(); // 自作アノテーションに必要なパラメータ }
自作アノテーションに挙動をつける
アノテーションを作っただけでは動きません。
処理を追加してあげる必要があります。
今回の例では、テストの実行前に動いてほしいので、SpringのTestExecutionListenerのbeforeTestMethodを継承しています。
Test実行後に動いてほしい、インスタンス生成時に動いてほしい等もTestExecutionListenerに用意されているので、それを継承すると良いでしょう。
挙動としては、TestExecutionListenerを継承しているので、どのテストが実行されても下記のメソッドが呼ばれます。
呼ばれたうえで、AnnotationUtilsを使用し、対象のテストクラスでDBSetupが付与されているかどうかを取得します。
nullであれば付与されていないので処理を終了する。
null以外であればpathの値をログに出力しています。
この値をもとに具体的な処理を記載しますが、アノテーションの使い方としては蛇足になるので説明を省きます。
@Slf4j public class DBSetupListener implements TestExecutionListener { @Override public void beforeTestMethod(TestContext testContext) throws Exception { DBSetup annotation = AnnotationUtils.findAnnotation(testContext.getTestMethod(), DBSetup.class); if (Objects.isNull(annotation)) { return; } // DBSetupの処理を書く。 log.info(annotation.path()); } }
テストでの使い方
SpringのTestExectuionListnerが必要になるので、SpringJunitConfigを使用しています。
TestExecutionListnersに自作したアノテーションの挙動を書いたクラスを記載し、テスト時に読み込みます。
@SpringJUnitConfig(AnotationTests.Config.class) @TestExecutionListeners({ // 自作アノテーションの挙動を記載したクラス DBSetupListener.class }) class AnotationTests { // 何もDIさせたくないので、適当な値を設定 @ComponentScan({"jp.co.kelly.biz.domain.hogehoge"}) public static class Config { } // "path"はアノテーションのメソッド名です @DBSetup(path = "ああああ") @Test void test_01() { System.out.println("キリン"); } @Test void test_02() { System.out.println("コアラ"); } }
なお、こちらのテストを実行すると、このような挙動になります。
ああああ // DBsetupでpathの値を出力 キリン // test_01 コアラ // test_02
終わりに
今回の記事で、自作アノテーションについてのイメージが湧けば幸いです。
個人的にアノテーションのことを深く知ろうとしなかったのって、どうやってアノテーションと挙動がどう紐づいているのか分からなかったからですね。
今回のアノテーションも一例でしかないから、いまだに良く分からないアノテーションに出会うことはあります…。
適切に使えば、アノテーション名で何をやっているのかを伝えられるので、非常に強力な武器になります。
ぜひ、使ってみてください。
もしこの記事が役に立ったのであれば、はてぶ、Twitterでの記事の拡散、twitterのフォローもよろしくお願いします。
Githubソース
表記揺れ吸収のため記載
これってどれが正しいですかね。