Spring Boot で DI を必要とする Validator のテストを高速化する
Spring Bootのプロジェクトでユニットテスト実行時間の高速化を模索している中で、DIが必要なカスタムバリデーターのテスト実行を高速化する方法が分かったので紹介します。
確認環境
- Spring Boot 2.2.2
- JUnit 5.5.2
- AdoptOpenJDK 11.0.3
- macOS Mojave 10.14.6 (3.1GHz Intel Core i5, 16 GB RAM)
背景
入力値の一意性をチェックする場合など、Bean Validation で DB 接続をしたい場合があり、次のような DI を使ったカスタムバリデーターをいくつも作っていました。
@Documented
@Constraint(validatedBy = { EmployeeCodeUniqueValidator.class })
@Target({ TYPE })
@Retention(RUNTIME)
public @interface EmployeeCodeUniqueConstraint {
...
}
public class EmployeeCodeUniqueValidator implements ConstraintValidator<EmployeeCodeUniqueConstraint, Object> {
private static final String EMPLOYEE_ID = "id";
private static final String EMPLOYEE_CODE = "employeeCode";
private final EmployeeDao employeeDao;
public EmployeeCodeUniqueValidator(final EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
String employeeCode = getField(value, EMPLOYEE_CODE);
if (employeeCode == null) {
return true;
}
Optional<Employee> employeeOpt = employeeDao.findByEmployeeCode(employeeCode);
if (employeeOpt.isEmpty()) {
return true;
}
...
しかし、このようなカスタムバリデーターのユニットテストを書く場合には、依存コンポーネントをモックで差し替えて DI した Validator を初期化する必要があります。そのため、これまでは @SpringBootTest
を使ったテストを書いてきましたが、Spring の初期化に時間がかかるため、カスタムバリデーターが増えるにつれて実行時間の増加に我慢できなくなってきました。
@SpringBootTest
class EmployeeCodeUniqueValidatorTest {
@Autowired
private Validator validator;
@MockBean
private EmployeeDao employeeDao;
@EmployeeCodeUniqueConstraint
class Form {
public String id;
public String employeeCode;
}
@Test
void newEmployeeCode() {
when(employeeDao.findByEmployeeCode("012345")).thenReturn(Optional.empty());
Form form = new Form();
form.employeeCode = "012345";
assertTrue(validator.validate(form).isEmpty());
}
...
}
解決策
@SpringBootTest
アノテーションの classes
属性を指定すると、Validator コンポーネントだけを初期化することができ、テスト起動時の初期化時間を短縮できます。
- @SpringBootTest
+ @SpringBootTest(classes = {ValidationAutoConfiguration.class})
class EmployeeCodeUniqueValidatorTest {
@Autowired
private Validator validator;
@MockBean
private EmployeeDao employeeDao;
...
}
2022/02/24 追記 初稿では、EmployeeDao.class も classes 属性に列挙していましたが、@ MockBean でDIするコンポーネントは列挙する必要がありませんでした。
改善効果
参考程度ですが、およそクラス数で30弱、メソッド数で500弱のバリデーター関連テストに対して適用し、実行時間が半分以下になりました
- 変更前: 78.5s
- 変更後: 29.8s
補足
テストの実行時間は速くなりますが、いちいち DI するクラスを列挙しなければならないのが玉に瑕です。カスタムバリデーターのテストクラスでは DI コンポーネントが少ないので何とかなりますが、多数の依存があるようなクラスでは実行速度よりもメンテナンスのしやすさを優先した方がよい可能性もあります。
また、チーム開発をしている場合にはこのような実装ルールの統一が難しく、知らないうちに @SpringBootTest
だけを指定したテストコードが増えがちです。 ArchUnit などを使って、機械的に気付ける仕組みを合わせて導入しておくとよさそうです。
@Test
void validatorTestShouldeRestrictAutowiredComponent() {
classes().that().haveNameMatching(".+ValidatorTest$").and().areAnnotatedWith(SpringBootTest.class)
.should()
.beAnnotatedWith(new DescribedPredicate<>("@SpringBootTest(classes = {ValidationAutoConfiguration.class, ...} でDIするコンポーネントを制限する") {
@Override
public boolean apply(JavaAnnotation annot) {
if (!annot.getRawType().getSimpleName().equals("SpringBootTest")) {
return true;
}
JavaClass[] classes = (JavaClass[]) annot.get("classes").or(new JavaClass[]{});
return Arrays.stream(classes)
.anyMatch(clazz -> clazz.getSimpleName().equals("ValidationAutoConfiguration"));
}
})
.check(ALL_CLASSES);
}
まとめ
この記事では、Spring Boot を使ったプロジェクトでカスタムバリデーターのユニットテストを高速化する方法を紹介しました。カスタムバリデーターのテスト実行が遅いことは以前から認識していましたが、ネットで検索してもなかなかこの方法にたどり着けなかったので、同じ悩みをかかえている人の参考になれば幸いです。
Author And Source
この問題について(Spring Boot で DI を必要とする Validator のテストを高速化する), 我々は、より多くの情報をここで見つけました https://qiita.com/oohira/items/5a9f0ff8123f3a4ea0ad著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .