如何写测试 五

Mock框架,一个好的软件设计,就是需要将实现的细节从业务代码中隔离出去。

就好比我们要测试的代码需要依赖数据库,那么肯定不能每次测试的时候,还有其他测试或者代码也在影响数据库,那么如果一个测试一个数据库,那么就太奢侈了。

所以我们是否可以通过模拟的方式,创建一个虚拟的数据库,这就是mock框架。

而mock框架,一开始是一种模式,后续才逐步延伸为一个框架。

而对于Mock框架,就是创建一个模拟对象并设置它的行为。这里的行为是指的当使用不同参数调用的时候,会根据入参给出的反馈。

也就是设置模拟对象,校验对象的行为。

如何设置模型对象,之前我们也做过。

TodoItemRepository repository = mock(TodoItemRepository.class);

然后对应的行为设置,代码的示例如下

when(repository.findAll()).thenReturn(of(new TodoItem(“foo”)));

when(repository.save(any())).then(returnsFirstArg());

when(repository.findByIndex(1)).thenReturn(new TodoItem(“foo”));

当调用说明参数,传入什么值的时候,返回什么。

这就是上面的代码逻辑。

并且在Mock框架之中,还有一个重要的行为,就是校验对象的结果。判断一个方法有没有按照预期的方法调用。并返回。或者判断一个方法有没有被调用

verify(repository).save(any());

但是verify按照我们之前所说,其实就是判断代码有没有被调用。

这在测试之中,应该减少使用。因为一旦设置了verify,之后基本就约束了函数的实现。测试的应该是接口的行为,而不是有没有被调用。

再其次,我们说下该如何书写一个单元测试。

单元测试是所有测试的基石,只有通过单元测试证明了单个组件的可靠性,才能够将多个单个组件组装,形成一个系统。

那么我们该如何书写单元测试?

一开始我们说过,没能做好测试的原因是因为单个组件的可测试性不足,导致没法形成一个包含所有组件的测试网。

这里首先给出一个建议,那就是不要尝试补测试。而是将代码和测试一起写。

这里说的一起写,是指的完成了一个功能所依赖的一个子函数之后,就可以进行测试代码的书写。

具体的编写流程可以为,首先确定一个实现类中的函数功能,其行为是么样的

TodoItem addTodoItem(final TodoParameter todoParameter);

根据这个接口,我们肯定要设计具体实现的时候的行为,比如保存的时候正常是什么样的,传入异常值是什么样的。

根据这些不同时期的行为,我们可以去设计具体的测试用例。

就比如正向的测试用例。

@Test

public void should_add_todo_item() {

TodoItemRepository repository = mock(TodoItemRepository.class);

when(repository.save(any())).then(returnsFirstArg());

TodoItemService service = new TodoItemService(repository);

TodoItem item = service.addTodoItem(new TodoParameter(“foo”));

assertThat(item.getContent()).isEqualTo(“foo”);

}

这样通过确认接口,书写接口对应函数,书写接口对应的单元测试,来将我们书写的多个子任务的代码,最终组合为了整体。并且确保整体是通过测试的。

再其次,书写单元测试的时候,应该注意是针对接口还是实现书写。

如果和实现契合度特别高的话,往往会出现如下的情况

@Test

public void should_add_todo_item() {

TodoItemRepository repository = mock(TodoItemRepository.class);

when(repository.save(any())).then(returnsFirstArg());

TodoItemService service = new TodoItemService(repository);

TodoItem item = service.addTodoItem(new TodoParameter(“foo”));

assertThat(item.getContent()).isEqualTo(“foo”);

verify(repository).save(any());

}

也就是说,在测试service的时候,必须要调用下层repository的save函数。

但是如果更换了repository的实现,那么就无法通过verify的校验。

这种强强绑定,注定会测试失败。

所以对于实际的项目之中,我更加倾向于接口测试,减少实际细节上的约束。

when(repository.save(any())).then(returnsFirstArg());

这样可以一定程度上降低未来代码重构时带来的影响。

那么我们本章介绍了,如何书写单元测试,很多团队因为多方面的原因,写的单元测试比较少。

这里也介绍了一个方法,就是单元测试和实现代码一同书写。从而避免后续补测试的痛苦。

而且在书写的时候,注重测试接口,而非实现。

发表评论

邮箱地址不会被公开。 必填项已用*标注