Mock框架简介
工欲善其事必先利其器,选择一个合适的Mock框架与手动实现Stub比,往往能够让我们的单测事半功倍。
需要说明的是,Mock框架并不是必须的。正如上文所说,我们可以实现Stub代码来隔离依赖,当需要使用到Mock对象时,我们只需要对Stub的实现稍作修改即可。
市面上有许多Mock框架可供选择,如常见的Mockito,PowerMock,Spock,EasyMock,JMock等。如何选择合适的框架呢?
如果你想半个小时就能上手,不妨试试Mockito,绝对如丝般顺滑!当然,如果有时间并且对Groovy语言感兴趣,不妨花半天时间了解下Spock,能让测试代码更加精简。
以下是几种常用的Mock框架对比,不知道怎么选时,不妨根据现状,需要注意的是,大部分Mock框架都不支持Mock静态方法。
单测实战
写单测一般包括3个部分,即Given(Mock外部依赖&准备Fake数据),When(调用被测方法)以及Then(断言执行结果),这种写法和Spock的语法结构也是一致的。
为了更好的理解单元测试,笔者将针对如下代码,分别使用Mockito和Spock写一个简单的示例,让大家感受一下两者的各自的特点和不同。
Copy@Service
@AllArgsConstructor
@NoArgsConstructor
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private EmailService emailService;
public ResultDTO<Long> addUser(AddUserRequest request) {
// 1. 校验参数
ResultDTO<Void> validateResult = validateAddUserParam(request);
if (!validateResult.isSuccess()) {
return ResultDTO.paramError(validateResult.getMsg());
}
// 2. 添加用户
UserDO userDO = request.buildUserDO();
long id = userDao.insert(userDO);
// 3. 添加成功,返回验证激活邮件
if (id > 0) {
emailService.sendVerifyEmail(request.getEmail());
return ResultDTO.success(id);
}
return ResultDTO.internalError("添加用户失败,请稍后重试");
}
/**
* 校验添加用户参数
*/
private ResultDTO<Void> validateAddUserParam(AddUserRequest request) {
if (Objects.isNull(request)) {
return ResultDTO.paramError("添加用户参数不能为空");
}
if (StringUtils.isBlank(request.getUserName())) {
return ResultDTO.paramError("用户名不能为空");
}
if (!EmailValidator.validate(request.getEmail())) {
return ResultDTO.paramError("邮箱格式错误");
}
return ResultDTO.success();
}
}
基于Mockito的单测示例如下,需要注意的下面是纯java代码,没有对象显示调用的方法都是已经静态导入过的。
Copy@RunWith(MockitoJUnitRunner.class)
public class UserServiceImplTest {
// Fake:需要提前构造的假数据
AddUserRequest fakeAddUserRequest;
// Mock: mock外部依赖
@InjectMocks
private UserServiceImpl userService;
@Mock
private UserDao userDao;
@Mock
private EmailService emailService;
@Before
public void init() {
fakeAddUserRequest = new AddUserRequest("zhangsan", "zhangsan@163.com");
when(userDao.insert(any())).thenReturn(1L);
when(emailService.sendVerifyEmail(anyString())).thenReturn(true);
}
@Test
public void testAddUser4NullParam() {
// GIVEN
fakeAddUserRequest = null;
// WHEN
ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
// THEN
assertEquals(addResult.getMsg(), "添加用户参数不能为空");
}
@Test
public void testAddUser4BadEmail() {
// GIVEN
fakeAddUserRequest.setEmail(null);
// WHEN
ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
// THEN
assertEquals(addResult.getMsg(), "邮箱格式错误");
}
@Test
public void testAddUser4BadUserName() {
// GIVEN
fakeAddUserRequest.setUserName(null);
// WHEN
ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
// THEN
assertEquals(addResult.getMsg(), "用户名不能为空");
}
@Test
public void testAddUser4DbError() {
// GIVEN
when(userDao.insert(any())).thenReturn(-1L);
// WHEN
ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
// THEN
assertEquals(addResult.getMsg(), "添加用户失败,请善后重试");
}
@Test
public void testAddUser4SendEmail() {
// GIVEN
// WHEN
ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
// THEN
assertTrue(addResult.isSuccess());
verify(emailService, times(1)).sendVerifyEmail(any());
verify(emailService).sendVerifyEmail(fakeAddUserRequest.getEmail());
}
}
正如上文提到的,Spock能够让代码更加精简,尤其是在代码逻辑分支比较多的场景下。下面是基于Spock的单测。
Copyclass UserServiceImplSpec extends Specification {
UserServiceImpl userService = new UserServiceImpl();
AddUserRequest fakeAddUserRequest;
def userDao = Mock(UserDao)
def emailService = Mock(EmailService)
def setup() {
// Fake数据创建
fakeAddUserRequest = new AddUserRequest(userName: "zhangsan", email: "zhangsan@163.com")
// 注入Mock对象
userService.userDao = userDao
userService.emailService = emailService
}
def "testAddUser4BadParam"() {
given:
if (Objects.isNull(userName) || Objects.is(email)) {
fakeAddUserRequest = null
} else {
fakeAddUserRequest.setUserName(userName)
fakeAddUserRequest.setEmail(email)
}
when:
def result = userService.addUser(fakeAddUserRequest)
then:
Objects.equals(result.getMsg(), resultMsg)
where:
userName | email | resultMsg
null | null | "添加用户参数不能为空"
"Java填坑笔记" | null | "邮箱格式错误"
null | "javaTKBJ@163.com" | "用户名不能为空"
}
def "testAddUser4DbError"() {
given:
_ * userDao.insert(_) >> -1L
when:
def result = userService.addUser(fakeAddUserRequest)
then:
Objects.equals(result.getMsg(), "添加用户失败,请稍后重试")
}
def "testAddUser4SendEmail"() {
given:
_ * userDao.insert() >> 1
when:
def result = userService.addUser(fakeAddUserRequest)
then:
result.isSuccess()
1 * emailService.sendVerifyEmail(fakeAddUserRequest.getEmail())
}
}
思考总结
在验证商业模式之前,时刻要想考虑投入产出比。时间和商业成本太高不利于产品快速推向市场,所以什么时候推广单测,需要更高阶的人决策。
测试不可能犯错误,单测也不例外。单测只测试程序单元自身的功能。因此,它不能发现集成错误、性能、或者其他系统级别的问题。
单测能够提高代码质量,驱动代码设计,帮助我们更早发现问题,保障持续优化和重构,是工程师的一项必备技能。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理