單元測試是項目開發中必不可少的一環,在 SpringBoot 的項目中,我們用 @SpringBootTest 註解來標註一個測試類,在測試類中注入這個接口的實現類之後對每個方法進行單獨測試。
比如下面這個示例測試類:
@SpringBootTest
public class HelloServiceTests {
@Autowired
private IHelloSerive helloService;
@Test
public void testHello() {
// ...
}
}
但是隨着項目的代碼量越來越大,你會發現測試類的啓動速度變得越來越慢,而大多數情況下只是爲了測試一下某個實現類的某個方法而已,比如測試一個DAO類的persist方法。
實際上, @SpringBootTest 註解還提供了兩個參數,好好利用這兩個參數就可以讓測試類的啓動速度變得更快。
1. webEnvironment
這個屬性決定了測試類要不要啓動一個 web 環境,說白了就是要不要啓動一個 Tomcat 容器,可選的值爲:
- MOCK, 啓動一個模擬的 Servlet 環境,這是默認值。
- RANDOM_PORT,啓動一個 Tomcat 容器,並監聽一個隨機的端口號
- DEFINED_PORT,啓動一個 Tomcat 容器,並監聽配置文件中定義的端口(未定義則默認監聽8080)
- NONE,不啓動 Tomcat 容器
如果你要測試的方法不需要用到 Tomcat 容器,比如:
- 測試一個 DAO 類的增刪改查
- 測試一個 Service 類的業務方法
- 測試一個 Util 類的公用方法
- 測試一個配置文件類是否讀取到了正確的值
- ... ...
只需要通過指定 @SpringBootTest(webEnvironment =
SpringBootTest.WebEnvironment.NONE) 即可達到加速的效果。這時測試類啓動時就只會初始化 Spring 上下文,不再啓動 Tomcat 容器了:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class HelloServiceTests {
@Autowired
private IHelloSerive helloService;
@Test
public void testHello() {
// ...
}
}
2. classes
classes 屬性用來指定運行測試類需要裝載的 class 集合,如果不指定,那麼會默認裝載 @SpringBootConfiguration 註解標註的類。
提到 @SpringBootConfiguration 你可能比較陌生,其實 @SpringBootApplication 的源碼裏就引入了這個註解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ...
}
也就是說,如果我們不指定classes屬性,那麼啓動測試類時需要加載的Bean的數量和正常啓動一次入口類(即有@SpringBootApplication註解的類)加載的 Bean 數量是一樣的。
如果你的項目中有很多個 Bean, 特別是有以下幾種時:
- 有 CommandLineRunner 的實現類
- 用 @PostConstruct 註解指定了初始化方法的類
這幾種類在程序初始化的過程中都會運行自身的業務代碼或者初始化代碼,從而延後了測試方法的運行。
在這種情況下,我們在編寫測試類的時候,如果明確這個測試類會用到哪幾個 Bean,則可以在 classes 屬性處指定,之後啓動測試類的時候,就只會加載需要的 Bean 到上下文中,從而加快啓動速度。比如:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes={HelloServiceImpl.class})
public class HelloServiceTests {
@Autowired
private IHelloService helloService;
@Test
public void testHello() {
// ...
}
}
即使此時項目中還有另外一個 Bean 在它的初始化方法裏寫了類似 Thread.sleep(10000) 等操作也不會影響,因爲這個 Bean 根本就沒有被加載和初始化。
最後要注意,如果你的 JUnit 的版本是 4.x 還需要在測試類上新增 @RunWith(SpringRunner.class) 註解。