什么是好的自动化测试?
测试的样子
我们用测试来保证代码的正确性,然而,测试的正确性如何保证呢?
测试保证代码的正确性,那测试代码的正确性也用测试保证?但你见过有人给测试写测试吗?没有。这是一个死循环问题。
既然给测试写测试行不通,那唯一可行的方案就是:
把测试写简单,简单到一目了然,不需要证明它的正确性。
所以简单的测试长什么样子。看一下 todo 的例子
@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");
// 清理(可选)
}
我们把测试分成四段,分别是准备、执行、断言和清理。
准备 为了测试做一些主备,例如启动外部依赖服务,存储预置数据。
执行 测试阶段最核心的部分,测试被测目标的行为。通常来说,它是一个测试点,一般来叔,执行应该就是一个函数调用。如果是测试外部系统,就是发出一个请求。
断言 断言是我们的预期,负责验证执行的结果是否正确。
清理 可选部分。如果测试用到了外部资源,在这个部分要及时释放,保证测试环境被还原到最初的状态。例如,我们在测试中向数据库插入了数据,执行完后要删除插入的数据。
如果准备和清理部分是测试用例通用的,可以放到
tUp 和 tearDown 里去完成。
这四个部分,必须存在的是 执行 和 断言。
不执行,目标都没有,还测什么?
不断言,没有预期,也是白跑。
A-TRIP
有了测试的基本结构,如何衡量测试有没有做好呢?
有人把好测试总结成了一个说法: A-TRIP,其实是5个单词的缩写
- Automatic,自动化;
- Thorough,全面的;
- Repeatable,可重复的;
- Independent,独立的;
- Professional,专业的。
Automatic,自动化 核心在自动化上,这也是为什么测试一定要有断言,因为只有在有断言的情况下,机器才能帮我们判断是否成功。
Thorough,全面 其实是对测试的要求,就是尽可能覆盖各种场景。另一个角度,就是测试覆盖率。
Repeatable,可重复 要求测试可以反复运行,并且结果都是一样的。这是保证测试简单可靠的前提,如果一个测试不可重复,那么我们将无法相信它的结果,测试也就失去了价值。
内存中执行的测试一般是可重复的。影响测试可重复性的因素主要是外部资源,例如文件、数据库、中间件、第三方服务等等。如果测试中遇到了这些外部资源,我们要想办法让这些资源在测试结束后,恢复原样。
文件可以采取临时文件的方式,如果是 JUnit5 可以使用 @TempDir
数据库可以采取事务,执行完毕回滚。
如果是第三方服务,可以采取模拟服务。
Independent,独立 测试和测试之间不应该有任何依赖.
什么是有依赖?什么叫有依赖?就是一个测试要依赖于另外一个测试运行的结果。比如两个测试都要依赖于数据库,第一个测试运行时往数据库里写了一些数据,而第二个测试在执行时要用到这些数据。也就是说,第二个测试必须在第一个测试执行之后再执行,这就叫做有依赖。
一些框架可以并行执行测试,测试之间有了依赖,测试就无法并行执行。
Professional,专业测试代码也是代码,也要按代码标准去维护。
测试的命名参考:
- should_ 测试场景;
- should_ 测试效果 _while_ 测试条件
例如,第一种命名,should_add_todo_item, 一般来说,这是正常情况的测试用例。
第二种命名表示在什么条件下,应该出现什么效果,比如
should_throw_exception_while_parameter_is_empty 可以用来描述异常。
总结
测试基本结构
- 准备
- 执行
- 断言
- 清理
测试质量衡量,A-TRIP
- Automatic
- Thorough
- Repeatable
- Independent
- Professional
如果今天的内容你只能记住一件事,那请记住:编写简单的测试。