小白搞 Spring Boot单元测试
内容是:Spring Boot 中的单元测
前言
何为单元测试
单元测试的目的: 测试当前所写的代码是否是正确的, 例如输入一组数据, 会输出期望的数据; 输入错误数据, 会产生错误异常等. 在单元测试中, 我们需要保证被测系统是独立的(SUT
没有任何的 DOC), 即当被测系统通过测试时, 那么它在任何环境下都是能够正常工作的. 编写单元测试时, 仅仅需要关注单个类就可以了.
而不需要关注例如数据库服务, Web 服务等组件。
背景
进行过JavaWeb
开发的同学都了解,在进行后台开发时不仅需要完成系统功能的开发,为了保证系统的健壮性还要同步编写对应的单元测试类。基于Spring Boot
开发的项目中的test包用于存放单元测试类,同时也提供了对应的注解来进行单元测试的编写,本文结合Mock对Spring Boot
中的单元测试进行总结。
环境:JDK1.8+
、Spring Boot
、mockito
。
单元测试的引入
在Spring Boot
中引入单元测试只需在pom文件中加入如下依赖,其中提供了JUnit、SpringBoot Test等常见单元测试库。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.0.111-beta</version>
</dependency>
单元测试的创建
每个单元测试类对应项目中的一个程序类,每个单元测试方法对应程序类中的一个方法,为保证所测试方法的正确性,至少需要设计四个以上的测试用例,包含:正确用例、错误用例和边界用例。编写的注释事项如下:
测试类的位置位于项目test包下,包的层级结构与项目相同; 测试类的命名规则通常为 xxxTest.java,其中xxx表示待测试类名; 测试类中方法命名规则为testXxx,其中Xxx表示待测试方法名 ; 测试方法上加上注解 @Test;
话不多说,咱们直接开干。
常用注解
当下是注解盛行时代,我们先来了解一下相关的几个注解。
注解 | 说明 |
---|---|
@RunWith |
更改测试运行器 , 缺省值org.junit.runner.Runner |
@Before |
初始化方法,执行当前测试类的每个测试方法前执行 |
@Test |
测试方法,在这里可以测试期望异常和超时时间 |
@Test(timeout = 10000) |
超时测试方法,若测试方法未在指定时间内结束则junit 自动将其标记为失败 |
@Transactional |
声明式事务管理,用于需数据库事务管理的测试方法 |
@Rollback(true) |
数据库回滚,避免测试数据污染数据库 |
相关理论和技术点,现在已经铺垫完成,下面,我们使用代码来实现。
代码实现
我们分别做三层的测试:controller、service、dao
Service层测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserServiceTest {
@Autowired
private UserService userService;
/**
* 测试获取用户
*/
@Test(timeOut = 300000)
@Transactional
public void testGetUser() {
UserEntity userEntity = userService.findByName("zhangSan");
Assert.assertNotNull(userEntity);
Assert.assertEquals("zhangSan", userEntity.getName());
}
}
是不是很简单呢?
Controller层测
controller层,也可以称之为网络请求测试。对于网络请求进行测试的情形多见于应用的Controller层。Spring测试框架提供MockMvc
对象,可以在不需要客户端-服务端请求的情况下进行Web测试.
测试开始之前需要建立测试环境,setup
方法被@Before
修饰。通过MockMvcBuilders
工具,创建一个MockMvc
对象。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
class UserControllerTest {
@Autowired
private UserController userController ;
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup(){
mockMvc = MockMvcBuilders.standaloneSetup(userController).build;
}
/**
* 获取用户列表
*/
@Test(timeOut = 300000)
public void testGetUserList() throws Exception {
String url = "/user/getUserList";
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
Assert.assertNotNull(mvcResult);
}
}
DAO层测试
由于DAO
层的方法直接操作数据库,为避免测试数据对数据库造成污染,使用注解@Transactional
和@Rollback
在测试完成后对测试数据进行回滚。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ScoreControllerTestNew {
@Autowired
private UserDao userDao;
/**
* 测试插入数据
*/
@Test
@Rollback(value = true)
@Transactional
public void testInsert() {
User userZhang = new User();
userZhang.setName("zhangSan");
userZhang.setAge(23);
userZhang.setGender(0);
userZhang.setEmail("123@test.com");
int n = userDao.insert(userZhang);
Assert.assertEquals(1, n);
}
}
到此,关于三个层面的测试就已经搞定了,下面我们来看看,如何使用Mockito
模拟数据库操作。
使用Mockito模拟数据库操作
前面在介绍web请求测试时使用了Mock技术,该技术常用于被测试模块(方法)依赖于外部系统(web服务、中间件或是数据库)时。
Mock
的中文译为仿制的,模拟的,虚假的。对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去。
Mockito
是当前最流行的 单元测试 Mock
框架。采用 Mock
框架,我们可以 虚拟 出一个 外部依赖,降低测试 组件 之间的 耦合度,只注重代码的 流程与结果,真正地实现测试目的。
由于web服务或数据库不可达时,可以对其进行Mock,在测试时不需要真实的模块也可完成测试。
常用的Mockito
方法如下:
方法 | 简介 |
---|---|
Mockito.mock(classToMock) |
模拟对象 |
Mockito.when(methodCall).thenReturn(value) |
参数匹配 |
Mockito.doReturn(toBeReturned).when(mock).[method] |
参数匹配(直接执行不判断) |
Mockito.when(methodCall).thenAnswer(answer)) |
预期回调接口生成期望值 |
Mockito.doNothing().when(mock).[method] |
不做任何返回 |
在使用Mockito
对DAO层的单元测试进行模拟后,得到的新的单元测试类如下 :
@RunWith(SpringRunner.class)
public class UserDaoTest {
@MockBean
private UserDao userDao;
private User userZhang = new User();
userZhang.setName("zhangSan");
userZhang.setAge(23);
@Before
public void setup() {
Mockito.when(userDao.findByName("zhangSan")).willReturn(userZhang);
Mockito.when(userDao.findByName("liSi")).willReturn(null);
}
@Test
public void testGetUser() {
Assert.assertEquals(userZhang, userDao.findByName("zhangSan"));
Assert.assertEquals(null, userDao.findByName("liSi"));
}
}
关于mockito
相关,请参考官网:https://site.mockito.org/
后记
本文重在用代码案例讲解单元测试,篇幅有限,先分享到这里,如有不当之处,敬请谅解指出。
推荐阅读