0%

测试用例中不要包含逻辑

编程语言给了我们充分的表达力。操作符、条件等概念是非常重要的工具,TA们使得我们可以写出处理范围极其广泛的输入。但,这种灵活性也带来了更多的复杂度,这会导致我们的程序更加难以被理解。
不像业务代码,在测试用例中简单比灵活更加重要。大多数的单元测试都是验证:一个确定的输入,产生一个确定的输出。测试可以通过直接列出输入和输出而不是计算他们,来显著的降低复杂度。不然,测试用例将很容易产生Bug。
我们一起看一个简单例子。下面这个测试用例,看起来是对的吧?

1
2
3
4
5
6
@Test public void shouldNavigateToPhotosPage() {
String baseUrl = "http://plus.google.com/";
Navigator nav = new Navigator(baseUrl);
nav.goToPhotosPage();
assertEquals(baseUrl + "/u/0/photos", nav.getCurrentUrl());
}

用例作者试图通过将一个公用的前缀作为一个变量,来避免重复。只是做一个字符串拼接,看起来也没啥不好的,但是如果我们把该变量inline来降低用例复杂度会怎么样呢?
1
2
3
4
5
@Test public void shouldNavigateToPhotosPage() {
Navigator nav = new Navigator("http://plus.google.com/");
nav.goToPhotosPage();
assertEquals("http://plus.google.com//u/0/photos", nav.getCurrentUrl()); // Oops!
}

把用例中没必要的计算剔除以后,bug看起来非常明显-我们竟然期待URL中有两个反斜杠!这个测试用例可能会失败,或者(更差的情况)在生产代码有同样bug的时候不正确的通过了测试。如果我们直接定义出输入和输出,而不是试图计算他们,我们将永远不会写出这样的用例。这还是个非常简单的例子-当我们加入更多的操作符或是包含循环和条件语句时,将更加难以确信测试用例是否正确。
另一个表达方式是这样的,
业务代码描述了一个根据输入计算输出的通用策略,测试用例则是输入/输出对的样例(这里的输出还可能会包含副作用,比如验证与其他类的交互)。通常来说判断一个输入/输出对是否正确是简单的,即使是用来计算的逻辑十分复杂。例如,很难描述出一段js代码根据服务端响应生成的dom树具体是什么样的。所以测试类似方法的理想方式是判断输出的html是否包含特定的字符串。
当测试用例确实需要逻辑的是否,这样的逻辑通常应该移出测试的主体结构,放进util或是helper方法中。由于这些helper方法可以相当复杂,对这些复杂的测试工具类做测试,是非常好的实践。

文章翻译自:

https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html