Spring中的單元測試
一、概述
二、集成測試????1.?常用注解????????1.1?@BootstrapWith????????1.2?@ContextConfiguration????????1.3?@WebAppConfiguration????????1.4?@ActiveProfiles????????1.5?@TestPropertySource????3.?單元測試????4.?Mock功能????5.?JUnit特性
一、概述
這一章講解如何使用 Spring 來整合測試框架,并且講解單元測試在 Spring 下的最佳實(shí)踐,Spring 團(tuán)隊(duì)非常注重測試驅(qū)動(dòng)開發(fā),Spring IoC 也可以使得測試變得非常簡單。
示例代碼框架版本:Spring 5 + JUnit 4
二、集成測試
集成測試最重要的一點(diǎn)是開發(fā)人員不需要將應(yīng)用部署到服務(wù)器或者連接其他服務(wù),就能將功能代碼進(jìn)行測試。
Spring 框架提供了spring-test模塊,在org.springframework.test包下提供了在 Spring 容器中集成測試的類,測試不依賴任何服務(wù)器和其他部署環(huán)境,所以它比簡單的單元測試慢,但比 Selenium 測試和部署到遠(yuǎn)程服務(wù)器上測試要快的多。
1. 常用注解
Spring 整合測試框架,提供了下面常用注解:
1.1@BootstrapWith
類級別注解,配置 Spring TestContext 框架如何引導(dǎo)啟動(dòng),一般不用配置,使用默認(rèn)的就行。
1.2@ContextConfiguration
類級別注解,集成測試中加載或配置ApplicationContext,使用locations參數(shù)指定基于XML配置的文件路徑,使用classes參數(shù)指定基于注解配置的配置類。
@ContextConfiguration("/test-config.xml")
public?class?XmlApplicationContextTests?{
????// class body...
}
@ContextConfiguration(classes = TestConfig.class)
public?class?ConfigClassApplicationContextTests?{
????// class body...
}1.3@WebAppConfiguration
類級別注解,集成測試中加載或配置WebApplicationContext,默認(rèn)從file:src/main/webapp作為根路徑加載 Web 應(yīng)用資源。
1.4@ActiveProfiles
類級別注解,在集成測試中,激活 Spring 中定義 Profile 來加載ApplicationContext,Profile 詳細(xì)用法參考文章Spring深入淺出IoC(下)。
@ContextConfiguration
@ActiveProfiles({"dev", "integration"})
public?class?DeveloperIntegrationTests?{
????// class body...
}1.5@TestPropertySource
類級別注解,在集成測試中,指定 Spring 下的配置文件或配置屬性。@TestPropertySource加載的配置屬性優(yōu)先級最高,比操作系統(tǒng)的環(huán)境變量,和使用@PropertySource注解聲明的配置等優(yōu)先級都要高,因此,如果這幾種配置方式中存在相同的屬性配置,則運(yùn)行時(shí)會(huì)以優(yōu)先級高的配置為準(zhǔn)。
它支持指定 classpath 路徑下的配置文件,或直接指定配置屬性:
@ContextConfiguration
@TestPropertySource("/test.properties")
public?class?MyIntegrationTests?{
????// class body...
}
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242"?})
public?class?MyIntegrationTests?{
????// class body...
}3. 單元測試
我們后面演示 Spring 項(xiàng)目應(yīng)用中,如何集成單元測試,使用最高效簡單的操作來演示基本的單元測試操作。
第一步:模擬一個(gè) Spring 應(yīng)用程序。主要是配置一個(gè)ApplicationContext上下文,因?yàn)槲覀冎?,?xiàng)目中所有 Bean 的配置入口都是在ApplicationContext容器里面進(jìn)行操作的。
package?cn.codeartis.spring;
@Configuration
@ComponentScan
public?class?TestApplicationContext?{
}這里我們使用一個(gè)簡單的基于注解配置的配置類,在該類同級目錄下編寫業(yè)務(wù)代碼如下:
// 業(yè)務(wù)接口
package?cn.codeartist.spring.service;
public?interface?DemoService?{
????void?service();
????void?service(String str);
}
// 業(yè)務(wù)實(shí)現(xiàn)類
@Service
public?class?DemoServiceImpl?implements?DemoService?{
????@Override
????public?void?service()?{
????????System.out.println("DemoServiceImpl.service");
????}
????@Override
????public?void?service(String str)?{
????????System.out.println("DemoServiceImpl.service::"?+ str);
????}
}第二步:編寫單元測試代碼。如果我們在 Spring 項(xiàng)目中使用的集成測試框架是一樣的,可以寫一個(gè)抽象類為父類來作為配置全局測試框架的類。
package?cn.codeartis.spring.test;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestApplicationContext.class)
public?abstract?class?AbstractSpringRunnerTests?{
}這里我們指定了一個(gè) Spring 的Runner和要測試的ApplicationContext上下文,對于熟悉 JUnit 測試工具的人來說對于Runner并不陌生,在該類同級目錄下編寫測試代碼如下:
package?cn.codeartis.spring.test.service;
public?class?DemoServiceTest?extends?AbstractSpringRunnerTests?{
????@Autowired
????private?DemoService demoService;
????@Test
????public?void?service()?{
????????demoService.service();
????}
}此時(shí)一個(gè)簡單的單元測試示例就完成了。
4. Mock功能
Spring 自帶集成了Mockito工具,我們可以使用它來簡單地實(shí)現(xiàn)業(yè)務(wù)代碼的 Mock 功能,因?yàn)樵跇I(yè)務(wù)中有調(diào)用第三方接口,或者使用微服務(wù),而只想測試自己的業(yè)務(wù)代碼,不管其他業(yè)務(wù)代碼,就可以使用 Mock 來解決這個(gè)問題。假如我們對前面的測試代碼進(jìn)行 Mock,代碼如下:
public?class?MockServiceTest?extends?AbstractSpringRunnerTests?{
????@MockBean
????private?DemoService demoService;
????@Before
????public?void?mock()?{
????????BDDMockito.doAnswer(invocation -> {
????????????System.out.println("Mock service.");
????????????return?null;
????????}).when(demoService).service();
????}
????@Test
????public?void?service()?{
????????demoService.service();
????}
}Mockito 的功能很強(qiáng)大,還支持其他 Mock 場景,這里我們不詳細(xì)講,后面我們會(huì)有單獨(dú)的文章來介紹。
5. JUnit特性
Spring Test 也支持 JUnit 的一些特性,比如:@Repeat、@Timed等注解的使用,還有其他測試方式,但這里我們要注意,前面我們在 Spring 單元測試中使用的Runner是SpringRunner,當(dāng)我們要在 Spring 中使用其他Runner時(shí),需要通過JUnit中的Rule來實(shí)現(xiàn),比如要在 Spring 中進(jìn)行參數(shù)測試:
@RunWith(Parameterized.class)
public?class?DemoParameterizedTest?extends?AbstractSpringRunnerTests?{
????
????@ClassRule
????public?static?final?SpringClassRule springClassRule = new?SpringClassRule();
????@Rule
????public?final?SpringMethodRule springMethodRule = new?SpringMethodRule();
????@Parameterized.Parameter
????public?String param;
????@Autowired
????private?DemoService demoService;
????@Parameterized.Parameters(name = "{index}: {0}")
????public?static?String[] data() {
????????return?new?String[]{"param1", "param2", "param3"};
????}
????@Test
????public?void?service()?{
????????demoService.service(param);
????}
}此時(shí)需要在測試類中聲明 Spring 提供的SpringClassRule和SpringMethodRule來集成 Spring 環(huán)境。
更多關(guān)于 JUnit4 的使用可以參考:《JUnit高效實(shí)踐》
參考文獻(xiàn)
Spring Framework 5.1.3 官方文檔:https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/
文章示例代碼地址:https://gitee.com/code_artist/spring



