测试分类
按不同分类可以有很多种,我这里简单说一下下面的几种,这里想要更深入了解的可以看这篇文章:https://zhuanlan.zhihu.com/p/81571007
单元测试目的:用于验证编码单元的正确性,比如测试某个方法逻辑正确性,属于白盒测试,即被测对象内部逻辑对测试者来说是透明的,一般由开发编写。
集成测试:用于验证详细设计,也叫组装测试、子系统测试,是在单元测试的基础上,将涉及到的上下游依赖、数据库、中间件、缓存等都访问真实内容,而不是单元测试的 mock 内容,将涉及到的模块都组装起来形成一个子系统,看这个子系统功能能不能正常服务,满足详细设计要求,属于黑盒测试。
系统测试目的:用于验证概要设计,测试每个系统功能的正确性,属于白盒测试,测试人员来做。
回归测试目的:验证缺陷得到了正确的修复,并且对系统的变更,没有影响以前的功能。一般是通过重新执行所有在前期测试阶段建立的测试用例,来确认问题修改的正确性。
Junit+Mockito+Assert实战
Mock场景
在实际开发中,为了完成我们的业务开发CRUD,我们的常操就是:
调用数据库获取数据,调用外部RPC获取数据,获取这些数据后做自己的业务逻辑开发返回给前端,这里我们依赖了数据库、外部RPC服务,所以我们在写单元测试的时候,经常做的操作就是需要用到Mock掉这些外部的依赖,保证这些外部依赖正常获取的的情况下我们的业务逻辑是正确的!
这里Mock的具体场景如下
- 最常用的 mock 场景是外部资源 RPC调用
- 拿数据库的连接,增删改查数据
- 下载文件
- 发邮件
- 调用打印机打印文件等
当测试涉及到以上内容的时候,没必要真正去调用这些资源,mockito 可以模拟这些外部资源调用。
实战模拟
这里我模拟一个日常开发中的业务场景带大家入门一下Mock是怎么使用的:
场景如下:我们需要调用UserService.getUserNameList获取用户列表返回给前端,我们想要验证的是getUserNameList这个方法的逻辑是否正确,getUserNameList主要逻辑如下:
1、调用数据库接口
2、业务逻辑计算:这里会涉及大量业务逻辑开发
3、调用第三方接口
这里我们的核心业务逻辑主要是在第2步,对userList进行了大量业务逻辑操作,所以我们想要验证的是在调用第3步调用第三方接口externalService.calculateUserInfo(userList)的时候,我这个userList的入参是否符合预期,也就是我第2步进行的业务逻辑开发是否正确
UserService:
public class UserService {
private UserDao userDao;
private ExternalService externalService;
public List<String> getUserNameList(Long userId) {
//模拟调用数据库接口 这里也需要mock
List<String> userList = userDao.listUserName();
userList.add("bb");
//模拟业务复杂的逻辑计算:对userList进行了大量操作
String uidStr = userId.toString();
userList.add(uidStr);
//模拟调用第三方接口 这里需要Mock掉
externalService.calculateUserInfo(userList);
userList.add("ccc");
return userList;
}
}
UserDao:
public class UserDao {
public List<String> listUserName() {
return new ArrayList<>(Arrays.asList("a", "b", "c"));
}
}
外部RPC服务ExternalService:
public class ExternalService {
public void calculateUserInfo(List<String> userList) {
userList.remove(0);
System.out.println("calculator" + userList);
}
}
Mock测试解析
我们这里mock两个场景,一个是正常调用成功的情况,一个是调用外部依赖失败的情况
正常调用成功
这里涉及到一些操作:
1、对于有参的mock,when(userDao.listUserName()).thenReturn(new ArrayList<>(Arrays.asList("a")));
2、对于无参的mock,doNothing().when(externalService).calculateUserInfo(anyList());
3、捕获入参:verify+ArgumentCaptor
1)定义入参捕获器
2)进行入参捕获
3)获取捕获的入参
1、定义入参捕获器
@Captor
private ArgumentCaptor<List<String>> listArgumentCaptor;
2、进行入参捕获
//入参捕获
verify(externalService).calculateUserInfo(listArgumentCaptor.capture());
3、获取捕获的入参
//获取捕获到的入参
//验证externalService.calculateUserInfo这里的入参是否符合预期
List<String> resultList = listArgumentCaptor.getValue();
4、验证结果:
assertEquals(resultList.get(0), "a");
assertEquals(resultList.get(1), "bb");
assertEquals(resultList.get(2), "999");
assertEquals(listArgumentCaptor.getValue().size(), 4);
完整代码:
@RunWith(MockitoJUnitRunner.class)
public class UserMockTest {
//要使用 injectMock注解进行注入
@InjectMocks
private UserService userService;
//
@Mock
private UserDao userDao;
@Mock
private ExternalService externalService;
//用于捕获入参
@Captor
private ArgumentCaptor<List<String>> listArgumentCaptor;
/**
* 这里service先调用userDao,然后还要调用外部服务externalService
* 要mock调两个 userDao 和rpc
* 最后是想要得到acm调用的时候的入参,因为这个入参在service里面有对应的复杂计算
*
* 1、当mock一个对象,且执行此对象中的方法有返回值时,使用下面的方法: when里面带上方法
* 类名 对象 = mock (类名.class);
* when (对象.方法 (参数)).thenReturn (方法的返回值);
* 2、 当mock一个对象,且执行此对象中的方法没有返回值时,使用下面的方法:when里面只能是对象
* 类名 对象 = Mockito.mock(类名.class); .when(对象).方法名();
* doNothing().when(acmService).calculateUserInfo(anyList());
*/
@Test
public void testGetUserNameListSucceed() {
//mock dao.listUserName()有参返回
when(userDao.listUserName()).thenReturn(new ArrayList<>(Arrays.asList("a")));
//mock externalService.calculateUserInfo 无参void方法 ,注意:这里anyList只是打桩 跟你下面获取入参没有关系
doNothing().when(externalService).calculateUserInfo(anyList());
//调用业务方法:这里内部会调用上面的mock
userService.getUserNameList(999L);
//入参捕获
verify(externalService).calculateUserInfo(listArgumentCaptor.capture());
//获取捕获到的入参
//验证externalService.calculateUserInfo这里的入参是否符合预期
List<String> resultList = listArgumentCaptor.getValue();
assertEquals(resultList.get(0), "a");
assertEquals(resultList.get(1), "bb");
assertEquals(resultList.get(2), "999");
assertEquals(listArgumentCaptor.getValue().size(), 4);
}
}
调用外部依赖失败
1、对于有参的mock,when(dao.listUserName()).thenThrow(new RuntimeException("mock exception"));
2、对于无参的mock,doThrow(new RuntimeException("mock exception")).when(externalService).calculateUserInfo(anyList());
3、try catch+assert验证结果:assertEquals("mock exception", e.getMessage());
/**
* Mock Annotation
* 测试:适用@Mock注解:@Mock 注解可以理解为对 mock 方法的一个替代。
* 使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效, 比如放在@Before方法中初始化。 比较优雅优雅的写法是用MockitoJUnitRunner,它可以自动执行MockitoAnnotations.initMocks 方法。
*/
@RunWith(MockitoJUnitRunner.class)
public class UserMockTest {
//要使用 injectMock注解进行注入
@InjectMocks
private UserService userService;
@Mock
private UserDao userDao;
@Mock
private ExternalService externalService;
//用于捕获入参
@Captor
private ArgumentCaptor<List<String>> listArgumentCaptor;
@Test
public void testGetUserNameListFailed() {
when(userDao.listUserName()).thenReturn(new ArrayList<>(Arrays.asList("a", "b", "c")));
//有参的模拟异常
//when(dao.listUserName()).thenThrow(new RuntimeException("mock exception") );
//无参的异常抛出
doThrow(new RuntimeException("mock exception")).when(externalService).calculateUserInfo(anyList());
try {
userService.getUserNameList(1232L);
} catch (Exception e) {
assertEquals("mock exception", e.getMessage());
}
}
}
推荐文章系列: