赞
踩
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。“单元”可以是一个函数、方法、类、功能模块或者子系统。 单元测试的核心在于行、分支覆盖率,不关注业务正确性
单元测试的实现方式包括:人工静态检查、动态执行跟踪。
人工静态检查包含的主要内容:
动态执行跟踪需要编写测试脚本调用业务代码进行测试,为了更好的管理维护测试脚本,一般会采用单元测试框架来管理,Java 常见的单元测试框架:JUnit、TestNG,其辅助依赖项有 Mockito、SpringBootTest;
单元测试的一个重要的衡量标准就是代码覆盖率,尽量做到代码的全覆盖。常见单元测试覆盖标准:
对于一个 Maven 项目,其结构如下
Gradle
Springboot 中单元测试类写在 src/test/java 目录下,可以进行手动创建测试类,或者通过 idea 自动创建测试类 ctrl+shift+T。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
SpringbootTest 使用单元测试需要先引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
test 依赖会引入如下的 jar 包
序号 | 库 | 作用 |
---|---|---|
1 | Junit5 | 包含兼容 Junit4,Java 应用程序单元测试的事实标准 |
2 | SpringTest 和 SpringBootTest | 对 SpringBoot 应用程序的公共和集成测试支持 |
3 | AssertJ | 断言库 |
4 | Hamcrest | 匹配对象库 |
5 | Mockito | Java 模拟框架 |
6 | JSONassert | JSON 断言库 |
7 | JsonPath | JSON XPath |
<properties> <!-- mock包 --> <powermock.version>2.0.2</powermock.version> </properties> <dependencies> <!-- 引入单元测试mock包 --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> </dependencies>
@RunWIth(SpringJunit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application.yaml"}
@ActiveProfiles("dev")
序号 | 方法 | 含义 |
---|---|---|
1 | void assertEquals(boolean expected, boolean actual) | 检查两个变量或者等式是否平衡 |
2 | void assertFalse(boolean condition) | 检查条件是假的 |
3 | void assertNotNull(Object object) | 检查对象不是空的 |
4 | void assertNull(Object object) | 检查对象是空的 |
5 | void assertTrue(boolean condition) | 检查条件为真 |
6 | void fail() | 在没有报告的情况下使测试不通过 |
注意:Junit4 和 Junit5 的注解不要混用
JUnit 4.4 结合 Hamcrest 提供了一个全新的断言语法——assertThat。可以只使用 assertThat 一个断言语句,结合 Hamcrest 提供的匹配符,就可以表达全部的测试思想。 assertThat 的优点:
基本语法:
assertThat([value], [matcher statement]);
value: 接下来想要测试的变量值;
matcher statement: 使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明,如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。
测试嵌套的方法,他的作用是我们把测试类封装起来,也就是把测试类嵌套起来,只需要运行测试套件,就能运行所有的测试类了。
// 这里有很多个测试类
public class Test1 {
@Test
public void test() {
System.out.println("测试类1");
}
}
public class Test2 {
@Test
public void test() {
System.out.println("测试类2");
}
}
// 这里依次可以类推
我们使用测试套件,把这些测试类嵌套在一起。
@RunWith(Suite.class)
@Suite.SuiteClasses({Test1.class,Test2.class等相关测试类})
public class SuiteTest {/*
* 写一个空类:不包含任何方法
* 更改测试运行器Suite.class
* 将测试类作为数组传入到Suite.SuiteClasses({})中
*/
}
如果要测试多组数据怎么办?总不能一个一个输入,然后运行测试吧。这时候我们可以把我们需要测试的数据先配置好。
@RunWith(Parameterized.class) public class ParameterizedTest { // 预期 int expected = 0; // 输入1 int input1 = 0; // 输入2 int input2 = 0; /** * 配置一组测试的数据 */ @Parameters public static Collection<Object[]> t() { return Arrays.asList(new Object[][]{{3, 1, 2}, {4, 2, 2}}); } public ParameterizedTest(int expected, int input1, int input2) { this.expected = expected; this.input1 = input1; this.input2 = input2; } @Test public void testAdd() { assertEquals(expected, Calculate.add(input1, input2)); } }
这时候再去测试,只需要去选择相应的值即可,避免了我们一个一个手动输入。
PowerMock 使用示例
@RunWith(PowerMockRunner.class)
@PrepareForTest(RedisUtil.class)
Powermockito.doNothing.when(T class2mock, String method, <T>… params>
Powermockito.whenNew(InstanceClass.class).thenReturn(Object value)
//final类
Powermockito.mockStatic(FinalClassToMock.class);
Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
//staic方法
Powermockito.mockStatic(StaticClassToMock.class);
Powermockito.when(StaticClassToMock.method(Object.. params)).thenReturn(Object value)
//object为需要设置属性的静态类或对象
Whitebox.setInternalState(Object object, String fieldname, Object… value);
InterfaceToMock mock = Powermockito.mock(InterfaceToMock.class);
Powermockito.when(mock.method(Params…)).thenReturn(value)
Powermockito.when(mock.method(Params..)).thenThrow(Exception)
参考
对 Controller 进行单元测试时,需要使用到 MockMvc 了。这样就可以不必启动项目就可以测试这些接口了。 MockMvc 实现了对 Http 请求的模拟,能够直接使用网络的形式,转换到 Controller 的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。 示例代码如下:
Controller
package com.unit.test.controller; import com.unit.test.bean.Student; import com.unit.test.service.StudentService; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/unit/test") public class JunitController extends BaseController { @Autowired private StudentService studentService; @GetMapping("/not/param") public String junitControllerNotParam() { return "没有请求参数的单元测试get方法"; } @GetMapping("/check/account") public String checkAccount() { String account = getAccount(); if (StringUtils.isNotBlank(account)) { return "成功"; } else { return "失败"; } } @GetMapping("/student") public Student getStudent(@RequestParam(value = "name") String name) { return studentService.getByName(name); } @GetMapping("/students") public List<Student> getStudents() { return studentService.listStudent(); } @PostMapping("/student") public String add(@RequestBody List<Student> students) { students.forEach(student -> studentService.save(student)); return "success"; } }
测试类
package com.unit.test.controller; import com.unit.test.bean.Student; import com.unit.test.service.StudentService; import com.unit.test.utils.JsonUtils; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBeans; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @Slf4j @SpringBootTest @RunWith(SpringRunner.class) public class Junit4ControllerTest { private JunitController junitController; @Autowired private WebApplicationContext webApplicationContext; @MockBean private StudentService studentService; private MockMvc mockMvc; @Before public void setUp() { log.info("setUp..."); // 初始化mockMvc对象 // 指定webApplicationContext上下文,将会从这个上下文获取对应的控制器并得到相应的mockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); log.info("setUp end..."); } @After public void tearDown() { log.info("@After"); } @Test public void junitControllerNotParam_404() throws Exception { MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/unit/test/get/has/param") .accept(MediaType.TEXT_HTML_VALUE) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andDo(MockMvcResultHandlers.print()) .andReturn(); int status = mvcResult.getResponse().getStatus(); Assert.assertEquals(404, status); } @Test public void junitControllerNotParam() throws Exception { // MockMvcRequestBuilders.get("/url"): 构造一个get请求 MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/unit/test/not/param") .accept(MediaType.TEXT_HTML_VALUE) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andDo(MockMvcResultHandlers.print()) .andReturn(); int status = mvcResult.getResponse().getStatus(); String content = mvcResult.getResponse().getContentAsString(); Assert.assertEquals(200, status); Assert.assertEquals("没有请求参数的单元测试get方法", content); } @Test public void getStudent() throws Exception { Student student = new Student(); student.setName("zhangsan"); student.setAge(13); Mockito.when(studentService.getByName("zhangsan")).thenReturn(student); // MockMvcRequestBuilders.get("/url"): 构造一个get请求 MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/unit/test/student") // 传参 .param("name", "zhangsan") // 请求类型,json .contentType(MediaType.APPLICATION_JSON_UTF8)) // 添加ResultHandler结果处理器,比如调试时 打印结果(print方法)到控制台 .andDo(MockMvcResultHandlers.print()) .andReturn(); int status = mvcResult.getResponse().getStatus(); Assert.assertEquals(200, status); String content = mvcResult.getResponse().getContentAsString(); Student convert = JsonUtils.str2obj(Student.class, content); Assert.assertEquals(13, convert.getAge().intValue()); Assert.assertEquals("zhangsan", convert.getName()); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。