Springboot单元测试
单元测试定义
单元测试是对软件基本组成单元进行的测试,如函数(function、procedure)或一个类的方法(method)。单元是最小的可测试软件组件,它具有一些基本属性,如明确的功能、规格定义、与其他部分的接口定义等,可清晰地与同一程序的其他单元划分,它通常执行单个内聚功能。单元测试是一种软件测试方法,通过该方法可以测试源代码的各个单元以确定它们是否适合使用。单元测试就是是指对这个最小可测试组件——即单元,进行检查和验证。
单元测试的目的
单元测试主要目的为了提高代码质量和可维护性(代码的可维护性是指增加一个新功能,或改变现有功能的成本,成本越低,可维护性即越高)。
单元测试拥有保证代码质量、尽早发现软件Bug、简化调试过程、促进变化并简化集成、使流程更灵活等优势。单元测试是针对代码单元的独立测试,核心是“独立”,优势来源也是这种独立性,而所面临的不足也正是因为其独立性:既然是“独立”,就难以测试与其他代码和依赖环境的相互关系。 单元测试与系统测试是互补而非代替关系。单元测试的优势,正是系统测试的不足,单元测试的不足,又恰是系统测试的优势。不能将单元测试当做解决所有问题的万金油,而需理解其优势与不足,扬长避短,与系统测试相辅相成,实现测试的最大效益。
一、Spring Boot中集成单元测试:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
在生成的测试类中就可以写单元测试了。用spring自带spring-boot-test的测试工具类即可, spring-boot-starter-test 启动器能引入这些 Spring Boot 测试模块:
JUnit:Java 应用程序单元测试标准类库。
Spring Boot Test:Spring Boot 应用程序功能集成化测试支持。
Mockito:Java 模拟测试框架。
AssertJ:一个轻量级的断言类库。
Hamcrest:匹配对象库。
JSONassert:JSON的断言库。
JsonPath:一个JSON操作类库。
如何使用
常用注解
@BeforeClass 全局只会执行一次,而且是第一个运行
@Before 在测试方法运行之前运行
@Test 测试方法 注解入参timeout = 20000 设置请求超时时间,也可设置返回异常预期(expected =异常类型)
@After 在测试方法运行之后允许
@AfterClass 全局只会执行一次,而且是最后一个运行
执行顺序 @BeforeClass -> @Before -> @Test -> @After -> @AfterClass
@Ignore 忽略此方法
@Transactional 在测试类或者方法上,开启事物执行结束后回滚
@Rollback 设置测试后回滚,默认属性为true即回滚,false不回滚。
@FixMethodOrder 设置测试方法的执行顺序修饰在类上入参为
(MethodSorters.JVM:代码定义的顺序、MethodSorters.DEFAULT:默认的顺序(无序随机)、MethodSorters.NAME_ASCENDING:方法名字母顺序)
二、Service单元测试
1、创建测试类,在测试类顶部添加注解@RunWith(SpringRunner.class) @SpringBootTest
@SpringBootTest:获取启动类,加载配置,寻找主配置启动类(被 @SpringBootApplication 注解的) @RunWith(SpringRunner.class):让JUnit运行Spring的测试环境,获得Spring环境的上下文的支持
2、在方法上添加@Test注解,测试时右键选择run或者debug该方法即可
@Test
public void test(){
System.out.print("hello world");
}
三、Controller单元测试
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。
package com.sensetime.idea.aix.biz.elevator.api.person;
import com.alibaba.fastjson.JSON;
import com.sensetime.idea.aix.biz.elevator.api.dm.ElevatorApi;
import com.sensetime.idea.aix.biz.elevator.vo.ElevatorPersonReq;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
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;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PersonControllerText {
@Autowired
ElevatorApi elevatorApi;
@Autowired
private WebApplicationContext wac;
private MockMvc mvc;
private MockHttpSession session;
@Before
public void setupMockMvc() {
//使用MockMvc的时候需要先用MockMvcBuilders使用构建MockMvc对象,如下
mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
session = new MockHttpSession();
}
/**
* 查询人员信息
* timeout = 20000 设置请求超时时间,也可设置返回异常预期(expected =异常类型)
*
*/
@Test(timeout = 20000)
public void getPersonList() throws Exception {
//mockMvc.perform执行一个请求
MvcResult mvcResult =
mvc.perform(MockMvcRequestBuilders.get("/api/elevator/v1/person")
.header("X-Id-Token", "")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.param("orgId", "310151101001")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
String contentAsString = response.getContentAsString();
//新断言语法判断
Assert.assertThat(contentAsString, containsString("code"));
System.out.println(response.getContentAsString());
}
/**
* 添加人员信息
*/
@Test
public void addPerson() throws Exception {
ElevatorPersonReq req = new ElevatorPersonReq();
req.setDueTime(0L);
req.setGender(-99);
req.setName("txj176_1");
req.setOrgId("310151101001");
req.setPersonSource(1);
req.setPersonType(1);
String jsonReq = JSON.toJSONString(req);
//mockMvc.perform执行一个请求
mvc.perform(MockMvcRequestBuilders.post("/api/elevator/v1/person")
.header("X-Id-Token", "")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(jsonReq)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
/**
* 修改人像信息
*/
@Test
public void updatePerson() throws Exception {
ElevatorPersonReq req = new ElevatorPersonReq();
req.setDueTime(0L);
req.setGender(-99);
req.setName("txj176_2");
req.setOrgId("310151101001");
req.setPersonSource(1);
req.setPersonType(1);
String jsonReq = JSON.toJSONString(req);
//mockMvc.perform执行一个请求
mvc.perform(MockMvcRequestBuilders.put
("/api/elevator/v1/person/663831284178620416")
.header("X-Id-Token", "")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(jsonReq)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
/**
* 删除人像信息
*
* @Rollback(value = false)关闭回滚
*/
@Test
@Transactional
public void deletePerson() throws Exception {
//mockMvc.perform执行一个请求
mvc.perform(MockMvcRequestBuilders.delete
("/api/elevator/v1/person/663842153025376256")
.header("X-Id-Token", "")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
System.out.print("hello word ");
}
/**
* 更新电梯图片#更新电梯图片
*/
@Test
public void image() throws Exception {
File file1=new File("/home/SENSETIME/zhenbaolei1.vendor/Pictures/zhen.png");
MockMultipartFile mfile = new MockMultipartFile("upload", "zhen.png",
MediaType.MULTIPART_FORM_DATA_VALUE, new FileInputStream(file1));
//mockMvc.perform执行一个请求
MvcResult mvcResult =
mvc.perform(MockMvcRequestBuilders.multipart("/api/elevator/image")
.file(mfile)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
System.out.println(response.getContentAsString());
}
}
mockMvc.perform执行一个请求
MockMvcRequestBuilders.get(“/user/1”)构造一个请求,Post请求就用.post方法
header(key,value)请求头
param(key,value)请求参数
content(String)请求参数json对象
contentType(MediaType.APPLICATION_JSON_UTF8)代表发送端发送的数据格式是application/json;charset=UTF-8
accept(MediaType.APPLICATION_JSON_UTF8)代表客户端希望接受的数据类型为application/json;charset=UTF-8
file()--上传文件
session(session)注入一个session,这样拦截器才可以通过
ResultActions.andExpect(MockMvcResultMatchers.status().isOk())方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过
ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
单元测试批量执行
1.批量执行单元测试类内的所有测试方法
a.测试时右键选择run或者debug该类名即可
b.mvn test -Dtest=类名(执行单个方法时:mvn test -Dtest=类名#方法名 )
2.整合测试单元测试类:只需要把相关的测试类加入到"{}"右键运行即可
@RunWith(Suite.class)
@Suite.SuiteClasses({ControllerBaseTest.class,CoreUtilsTest.class
})
public class testAll {
}
3.使用mvn命令运行所有@test修饰的方法
a.命令:mvn test
b.命令:mvn test surefire-report:report(执行所用测试方法并生成测试报告【报告目录在/target/site/surefire-report.html】
断言的概念
1. 断言(assert),是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。
2. 使用断言是判断一个函数或对象的一个方法所产生的结果是否符合你期望那个结果。
新断言assertThat使用
JUnit4之前提供了很多的 assertion 语句,如:assertEquals,assertNotSame,assertFalse,assertTrue,assertNotNull,assertNull 等,现在有了 JUnit 4.4,一条 assertThat 即可以替代所有的 assertion 语句,
这样可以在所有的单元测试中只使用一个断言方法,使得编写测试用例变得简单,代码风格变得统一,测试代码也更容易维护。
基本语法:assertThat( [value], [matcher statement] );
value 是接下来想要测试的变量值,matcher statement 是使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明,如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。
具体参考:https://www.cnblogs.com/wangcp-2014/p/4967055.html
单元测试回滚
单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,测试方法或者类头部添加@Transactional,@Rollback 注解即可
如果想关闭回滚,只要加上@Rollback(false)注解即可。@Rollback表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。
注释:在分布式系统中回滚无法保证事物的一致性