前面一個部分講解了如何使用Spring Testing工具來測試Spring項目,現在我們講解如何使用Spring Boot Testing工具來測試Spring Boot項目。
在Spring Boot項目里既可以使用Spring Boot Testing工具,也可以使用Spring Testing工具。
在Spring項目里,一般使用Spring Testing工具,雖然理論上也可以使用Spring Boot Testing,不過因為Spring Boot Testing工具會引入Spring Boot的一些特性比如AutoConfiguration,這可能會給你的測試帶來一些奇怪的問題,所以一般不推薦這樣做。
例子1:直接加載Bean
使用Spring Boot Testing工具只需要將@ContextConfiguration
改成@SpringBootTest
即可,源代碼見FooServiceImpltest:
@SpringBootTest(classes = FooServiceImpl.class)
public class FooServiceImplTest extends AbstractTestNGSpringContextTests { @Autowired
private FooService foo;
@Test
public void testPlusCount() throws Exception {
assertEquals(foo.getCount(), 0);
foo.plusCount();
assertEquals(foo.getCount(), <span class="hljs-number">1</span>);
}
}
例子2:使用內嵌@Configuration加載Bean
源代碼見FooServiceImpltest:
@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests { @Autowired
private FooService foo;
@Test
public void testPlusCount() throws Exception {
assertEquals(foo.getCount(), 0);
foo.plusCount();
assertEquals(foo.getCount(), <span class="hljs-number">1</span>);
}
@Configuration
@Import(FooServiceImpl.class)
static class Config {
}
}
例子3:使用外部@Configuration加載Bean
@Configuration
@Import(FooServiceImpl.class)
public class Config {
}
@SpringBootTest(classes = Config.class)
public class FooServiceImplTest extends AbstractTestNGSpringContextTests { @Autowired
private FooService foo;
@Test
public void testPlusCount() throws Exception {
assertEquals(foo.getCount(), 0);
foo.plusCount();
assertEquals(foo.getCount(), <span class="hljs-number">1</span>);
}
}
這個例子和例子2差不多,只不過將@Configuration放到了外部。
例子4:使用@SpringBootConfiguration
前面的例子@SpringBootTest
的用法和@ContextConfiguration
差不多。不過根據@SpringBootTest
的文檔:
它會嘗試加載
@SpringBootTest(classes=...)
的定義的Annotated classes。Annotated classes的定義在ContextConfiguration中有說明。如果沒有設定
@SpringBootTest(classes=...)
,那么會去找當前測試類的nested @Configuration class如果上一步找到,則會嘗試查找
@SpringBootConfiguration
,查找的路徑有:1)看當前測試類是否@SpringBootConfiguration
,2)在當前測試類所在的package里找。
所以我們可以利用這個特性來進一步簡化測試代碼。
@SpringBootConfiguration
@Import(FooServiceImpl.class)
public class Config {
}
@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests { @Autowired
private FooService foo;
@Test
public void testPlusCount() throws Exception {
assertEquals(foo.getCount(), 0);
foo.plusCount();
assertEquals(foo.getCount(), <span class="hljs-number">1</span>);
}
}
例子5:使用@ComponentScan掃描Bean
前面的例子我們都使用@Import
來加載Bean,雖然這中方法很精確,但是在大型項目中很麻煩。
在常規的Spring Boot項目中,一般都是依靠自動掃描機制來加載Bean的,所以我們希望我們的測試代碼也能夠利用自動掃描機制來加載Bean。
@SpringBootConfiguration
@ComponentScan(basePackages = "me.chanjar.basic.service")
public class Config {
}
@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests { @Autowired
private FooService foo;
@Test
public void testPlusCount() throws Exception {
assertEquals(foo.getCount(), 0);
foo.plusCount();
assertEquals(foo.getCount(), <span class="hljs-number">1</span>);
}
}
例子6:使用@SpringBootApplication
也可以在測試代碼上使用@SpringBootApplication
,它有這么幾個好處:
自身
SpringBootConfiguration
提供了
@ComponentScan
配置,以及默認的excludeFilter,有了這些filter Spring在初始化ApplicationContext的時候會排除掉某些Bean和@Configuration啟用了
EnableAutoConfiguration
,這個特性能夠利用Spring Boot來自動化配置所需要的外部資源,比如數據庫、JMS什么的,這在集成測試的時候非常有用。
@SpringBootApplication(scanBasePackages = "me.chanjar.basic.service")
public class Config {
}
@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests { @Autowired
private FooService foo;
@Test
public void testPlusCount() throws Exception {
assertEquals(foo.getCount(), 0);
foo.plusCount();
assertEquals(foo.getCount(), <span class="hljs-number">1</span>);
}
}
避免@SpringBootConfiguration沖突
當@SpringBootTest
沒有定義(classes=...
,且沒有找到nested @Configuration class的情況下,會嘗試查詢@SpringBootConfiguration
,如果找到多個的話則會拋出異常:
Caused by: java.lang.IllegalStateException: Found multiple @SpringBootConfiguration annotated classes [Generic bean: class [...]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/qianjia/workspace-os/spring-test-examples/basic/target/test-classes/me/chanjar/basic/springboot/ex7/FooServiceImplTest1.class], Generic bean: class [me.chanjar.basic.springboot.ex7.FooServiceImplTest2]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [...]]
比如以下代碼就會造成這個問題:
@SpringBootApplication(scanBasePackages = "me.chanjar.basic.service")
public class Config1 {
} @SpringBootApplication(scanBasePackages = "me.chanjar.basic.service")
public class Config2 {
}
@SpringBootTest
public class FooServiceImplTest extends AbstractTestNGSpringContextTests {
// ...
}
解決這個問題的方法有就是避免自動查詢@SpringBootConfiguration
:
定義
@SpringBootTest(classes=...)
提供nested @Configuration class
最佳實踐
除了單元測試(不需要初始化ApplicationContext的測試)外,盡量將測試配置和生產配置保持一致。比如如果生產配置里啟用了AutoConfiguration,那么測試配置也應該啟用。因為只有這樣才能夠在測試環境下發現生產環境的問題,也避免出現一些因為配置不同導致的奇怪問題。
在測試代碼之間盡量做到配置共用,這么做的優點有3個:
能夠有效利用Spring TestContext Framework的緩存機制,ApplicationContext只會創建一次,后面的測試會直接用已創建的那個,加快測試代碼運行速度。
當項目中的Bean很多的時候,這么做能夠降低測試代碼復雜度,想想如果每個測試代碼都有一套自己的@Configuration或其變體,那得多嚇人。
參考文檔
</div>
Powered by .NET Core on Kubernetes