概要
SpringBootのテストスライスアノテーションについて、どのような実装になっているのか探ってみた。
テストスライスアノテーションとは?
まず、ここでのテストスライスアノテーションは、アプリケーションの全量ではなく一部だけをテストしたい、といった場合に利用するアノテーションのことを指す。(このページにSpringBoot公式で提供されている全量が記載されている)
テストスライスアノテーションを利用する動機としては、ユニットテスト実行時間は最小限に抑えつつ、意味のあるユニットテストをしたい、という希望を叶えることにある。
SpringBootで作成するWebアプリケーションであれば、リクエスト・レスポンスのバリデーションや変換を受け持つController層、ビジネスロジックを持つService層、DB等へのアクセスを受け持つRepository層に分けて作成を行う。
この前提のもと、例としてController層のテストを考える。@SpringBootTest
で全てのユニットテストを書けば、本番稼働の状況と限りなく近づくが、テスト実行時間は増える。一方、Controller層で依存しているものを全てモックに差し替えてテストを行った場合、テスト実行時間は短いが、本番稼働の状況からは遠のく。
@WebMvcTest
を利用すれば、リクエストのバリデーションやJsonデシリアライズ、レスポンスのJsonシリアライズやエラーハンドリングについて本番相当の部品でテストでき、実行時間も@SpringBootTest
よりは短くて済む。
テストスライスアノテーションの中身
テストスライスアノテーションは実際どのように実装されているのかみていく。例として@DataJpaTest
をみていくが、基本的な構造は他のテストスライスアノテーションでもほぼ一緒である。
DataJpaTest
SpringBoot2.7.5だと下記のよう。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
特徴的なところをみていく。
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@BootstrapWith
ではテスト実行時のアプリケーションコンテキストを作成するにあたって、どこを起点に作成するかを決める。
DataJpaTestContextBootstrapper
は SpringBootTestContextBootstrapper
を継承しているが、SpringBootTestContextBootstrapper
では@DataJpaTest
を付与したパッケージから遡って行って、@SpringBootConfiguration
が付与されたクラスを起点に選ぶ。なお、@SpringBootConfiguration
は@SpringBootApplication
に内包されている。
protected Class<?>[] getOrFindConfigurationClasses(MergedContextConfiguration mergedConfig) {
Class<?>[] classes = mergedConfig.getClasses();
if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
return classes;
}
Class<?> found = new AnnotatedClassFinder(SpringBootConfiguration.class) // <- 起点である SpringBootConfiguration.class を探している
.findFromClass(mergedConfig.getTestClass());
Assert.state(found != null, "Unable to find a @SpringBootConfiguration, you need to use "
+ "@ContextConfiguration or @SpringBootTest(classes=...) with your test");
logger.info("Found @SpringBootConfiguration " + found.getName() + " for test " + mergedConfig.getTestClass());
return merge(found, classes);
}
そのため、@DataJpaTest
では通常@SpringBootApplication
が付与されたクラスを起点にアプリケーションコンテキストを作成することになる。
@SpringBootConfiguration
へのたどり方は、対象のテストクラスから1つずつパッケージを上がっていって見つける。
そのため、テストクラスがcom.example.slicetest.demo
にあったとしたら、@SpringBootApplication
を付与したクラスは com.example.slicetest
, com.example
, com
のいずれかに配置されている必要がある。
もし、com.example.slicetest.application
などに配置されているとエラーになる。
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@SpringBootApplication
でアプリケーションコンテキストを作る際に、bean登録しないクラスを選別するための設定。
DataJpaTypeExcludeFilter
の中身は下記。
public final class DataJpaTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter<DataJpaTest> {
DataJpaTypeExcludeFilter(Class<?> testClass) {
super(testClass);
}
}
上記のようにDataJpaTypeExcludeFilter
はStandardAnnotationCustomizableTypeExcludeFilter
を継承しているが、特に新しく設定を追加してはいない。
StandardAnnotationCustomizableTypeExcludeFilter
をそのまま利用すると、@SpringBootApplication
を付与したクラスからの@ComponentScan
はされなくなる。
つまり、@Controller
、@Service
、@Component
、@Repository
によるBean定義はアプリケーションコンテキストに入らなくなり、@Configuration
を付与したクラスでのBean定義も効かなくなる。
DataJpaTypeExcludeFilter
と同列である、WebMvcTypeExcludeFilter
では下記のようにComponentScanの対象となるアノテーションやクラスを明示するようにしている。
(ExcludeFilterという名前であったので、てっきり除外するものを列挙しているのだろうと思ったが、逆にscanするものを列挙してそれ以外は省く、という発想であった)
Set<Class<?>> includes = new LinkedHashSet<>();
includes.add(ControllerAdvice.class);
includes.add(JsonComponent.class);
includes.add(WebMvcConfigurer.class);
includes.add(WebMvcRegistrations.class);
includes.add(javax.servlet.Filter.class);
includes.add(FilterRegistrationBean.class);
includes.add(DelegatingFilterProxyRegistrationBean.class);
includes.add(HandlerMethodArgumentResolver.class);
includes.add(HttpMessageConverter.class);
includes.add(ErrorAttributes.class);
includes.add(Converter.class);
includes.add(GenericConverter.class);
includes.add(HandlerInterceptor.class);
@AutoConfigure~~~~
DataJpaTest
では下記が設定されている。
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
上記アノテーションによって、META-INF/spring/~~~.imports
ファイルに含まれたクラスをauto configureする。
ファイル名とauto configureするクラスの対応は下記の通り。
- org.springframework.boot.test.autoconfigure.core.AutoConfigureCache.imports
- org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
- org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa.imports
- org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
- org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
- org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
- org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
- org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
- org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.imports
- org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager.imports
- org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration
@DataJpaTest
は@Repository
を付与したクラスをアプリケーションコンテキストに入れるが、その設定はどこにあるのかというと、JpaRepositoriesAutoConfiguration
にて行っている。
まとめ
DataJpaTest
を中心にテストスライスアノテーションの実装について調査した。
調べた結果、テストスライスアノテーションの肝となる箇所は、
-
@TypeExcludeFilters
でどのアノテーション、クラスをアプリケーションコンテキストに入れるか -
@AutoConfigure~~
で何をauto configureするか
であるということが分かった。
参考