JUnit 5 概述:
要说什么是 JUnit 5,首先就得聊下 Java 单元测试框架 JUnit,它与另一个框架 TestNG 占据了 Java领域里单元测试框架的主要市场,其中 JUnit 有着较长的发展历史和不断演进的丰富功能,备受大多数 Java 开发者的青睐。
JUnit 起源于 1997年,最初版本是由两位编程大师 Kent Beck 和 Erich Gamma 的一次飞机之旅上完成的,由于当时 Java 测试过程中缺乏成熟的工具,两人在飞机上就合作设计实现了 JUnit 雏形,旨在成为更好用的 Java 测试框架。如今二十多年过去了,JUnit 经过各个版本迭代演进,已经发展到了 5.x 版本,为 JDK 8以及更高的版本上提供更好的支持 (如支持 Lambda ) 和更丰富的测试形式 (如重复测试,参数化测试)。
了解过 JUint 之后,再回头来看下 JUnit 5,这个版本可以说是 JUnit 单元测试框架的一次重大升级,首先需要 Java 8 以上的运行环境,虽然在旧版本 JDK 也能编译运行,但要完全使用 JUnit 5 功能, JDK 8 环境是必不可少的。
自从有了类似 JUnit 之类的测试框架,Java 单元测试领域逐渐成熟,开发人员对单元测试框架也有了更高的要求:更多的测试方式,更少的其他库的依赖。因此,大家期待着一个更强大的测试框架诞生,JUnit 作为Java测试领域的领头羊,推出了 JUnit 5 这个版本,主要特性:
- 提供全新的断言和测试注解,支持测试类内嵌
- 更丰富的测试方式:支持动态测试,重复测试,参数化测试等
- 实现了模块化,让测试执行和测试发现等不同模块解耦,减少依赖
- 提供对 Java 8 的支持,如 Lambda 表达式,Sream API等。
测试用例
引入JUnit 5 依赖,我们可以先快速编写一个简单的测试用例,从这个测试用例来认识初步下 JUnit 5:
JUnit 4 和 JUnit 5 的差异
1. 忽略测试用例执行:
@Test @Ignore public void testMethod() { // ... }
@Test @Disabled("explanation") public void testMethod() { // ... }
2. RunWith
配置:
@RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTests { @Test public void contextLoads() { } }
@ExtendWith(SpringExtension.class) @SpringBootTest public class ApplicationTests { @Test public void contextLoads() { } }
3. @Before
、@BeforeClass
、@After
、@AfterClass
被替换
@BeforeEach
替换@Before
@BeforeAll
替换@BeforeClass
@AfterEach
替换@After
@AfterAll
替换@AfterClass
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.caculate</groupId> <artifactId>caculate</artifactId> <version>0.0.1-SNAPSHOT</version> <name>caculate</name> <description>caculate</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 工程创建好之后自动生成了一个测试类。
package com.calculatorss; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class CaculateApplicationTests { @Test void contextLoads() { } }
这个测试类的作用是检查应用程序上下文是否可正常启动。@SpringBootTest
注解告诉 Spring Boot 查找带 @SpringBootApplication
注解的主配置类,并使用该类启动 Spring 应用程序上下文。
- 补充待测试应用逻辑代码
4.1. 定义 Service 层接口
package com.calculatorss.service; /** * @author Reese * @date 2020-08-19 */ public interface HelloService { String hello(String name); }
4.2. 定义 Controller 层
package com.calculatorss.controller; import com.calculatorss.service.HelloService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @author Reese * @date 2020-08-19 */ @RestController public class HelloController { private final HelloService helloService; public HelloController(HelloService helloService) { this.helloService = helloService; } @GetMapping("/hello/{name}") public String hello(@PathVariable("name") String name) { return helloService.hello(name); } }
4.3. 定义 Service 层实现
package com.calculatorss.service; import org.springframework.stereotype.Service; /** * @author Reese * @date 2020-08-19 */ @Service public class HelloServiceImpl implements HelloService { @Override public String hello(String name) { return "Hello, " + name; } }
- 编写发送 HTTP 请求的单元测试。
package com.calculatorss; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; /** * @author Reese * @date 2020-08-19 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HttpRequestTest { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; @Test public void testHello() { String requestResult = this.restTemplate.getForObject("http://127.0.0.1:" + port + "/hello/spring", String.class); Assertions.assertThat(requestResult).contains("Hello, spring"); } }
说明:
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
使用本地的一个随机端口启动服务;@LocalServerPort
相当于@Value("${local.server.port}")
;- 在配置了
webEnvironment
后,Spring Boot 会自动提供一个TestRestTemplate
实例,可用于发送 HTTP 请求。 - 除了使用
TestRestTemplate
实例发送 HTTP 请求外,还可以借助org.springframework.test.web.servlet.MockMvc
完成类似功能,代码如下:
package tutorial.spring.boot.junit5.controller; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @SpringBootTest @AutoConfigureMockMvc public class HelloControllerTest { @Autowired private HelloController helloController; @Autowired private MockMvc mockMvc; @Test public void testNotNull() { Assertions.assertThat(helloController).isNotNull(); } @Test public void testHello() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.get("/hello/spring")) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("Hello, spring")); } }
以上测试方法属于整体测试,即将应用上下文全都启动起来,还有一种分层测试方法,譬如仅测试 Controller 层。
- 分层测试。
package com.calculatorss; import com.calculatorss.controller.HelloController; import com.calculatorss.service.HelloService; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; /** * @author Reese * @date 2020-08-19 */ @WebMvcTest public class HelloControllerTest { @Autowired private HelloController helloController; @Autowired private MockMvc mockMvc; @MockBean private HelloService helloService; @Test public void testNotNull() { Assertions.assertThat(helloController).isNotNull(); } @Test public void testHello() throws Exception { Mockito.when(helloService.hello(Mockito.anyString())).thenReturn("Mock hello"); this.mockMvc.perform(MockMvcRequestBuilders.get("/hello/spring")) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string("Mock hello")); } }
说明:
@WebMvcTest
注释告诉 Spring Boot 仅实例化 Controller 层,而不去实例化整体上下文,还可以进一步指定仅实例化 Controller 层的某个实例:@WebMvcTest(HelloController.class)
;- 因为只实例化了 Controller 层,所以依赖的 Service 层实例需要通过
@MockBean
创建,并通过Mockito
的方法指定 Mock 出来的 Service 层实例在特定情况下方法调用时的返回结果。
SpringBoot 整合 JUnit 5:
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库,在 Spring Boot 2.2.0 版本之前,spring-boot-starter-test
包含了 JUnit 4 的依赖,Spring Boot 2.2.0 版本之后替换成了 Junit Jupiter。
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
使用测试注解:
@ExtendWith(SpringExtension.class) // 使用JUnit5测试工具 @WebAppConfiguration // 启动WEB运行环境 @SpringBootTest(classes = TestDemoApplication.class) // 配置程序启动类 public class RestHighLevelClientTest { // 编写测试函数 // ...... }