查尔斯推荐的单体测试的六条规则:
● 先写测试
● 从不写第一次就成功的测试
● 从空的或不能工作的用例开始
● 不要为做些可以让测试运行的琐屑的事情而担心
● 低耦合和可测试性是密切联系的
● 使用mock对象
先写测试
这是极限编程的格言,我的经验也说明这样做有效。首先编写测试以及可以让测试编译通过的足够的应用程序代码(不要太多哦!)。然后你运行测试来证明它无法工作(参照如下的第二点)。然后你写可以让那个测试通过的足够的代码(参照如下的第四点)。然后你写另外一个测试。
这种方法的好处来自你写代码的方法。你代码的每一部分都是目标驱动的。为什么我要写这行代码啊?我写是为了这样测试就可以运行。为了让测试通过我应该做些什么啊?我必须写这行代码。你会一直写一些让你程序可以完全工作的东西。
此外,先写测试意味着你在开始编码前必须决定如何让你的代码可测试。由于你在有一个覆盖代码的测试前你不会写任何代码,你就不会写任何不可测试的代码。
从不写第一次就成功的测试
写完测试后,马上运行它。它应该失败。科学的本质是证伪。写一个第一次就通过的测试不能证明任何东西。证明你的测试不是测试成功的绿色横条可以,而是从红色横条变成绿色的过程。每次我写了一个第一次就正确运行的测试,我就会怀疑它。没有代码会第一次就正确运行的。
从空的或不能工作的用例开始
从什么地方开始往往是一个障碍点。你如果想着针对一个方法运行的第一个测试,可以选择一些简单甚至琐碎的。有没有一个环境可以让该方法返回null,或者一个空的集合,或者一个空的数组?先测试这个用例。你的方法是不是会到数据库查询点东西?然后就可以测试如果查询一些不存在的东西会发生什么。
通常从最简单的测试写起,它们可以给你一个编写更复杂交互的良好起点。它们可以让你起步。
不要为做些可以让测试运行的琐屑的事情而担心
这样你根据第三点写了如下测试:
public void testFindUsersByEmailNoMatch() { assertEquals( "nothing returned", 0, new UserRegistry().findUsersByEmail("not@in.database").length); } |
很显然,可以让这个测试通过的最小量的代码如下:
public User[] findUsersByEmail(String address) { return new User[0]; } |
写这样的就为了让测试通过的代码的自然反应可能会是,“这简直是欺骗!”。这不是欺骗,因为编写查询一个用户以确认他不在那里的代码往往会是在你开始查找用户时一种自然扩展。
你真正要做的事情就是通过加上简单的代码并使得测试从失败到成功来证明测试可以工作。然后,如果你写了testFindUsersByEmailOneMatch和testFindUsersByEmailMultipleMatches,测试就是保护你并保证你不会在一些小的用例中改变行为,保证你不会偶然抛出异常或返回null。
第三点和第四点共同为你提供一个测试的基础,从而让你在开始处理重要用例时候不会忘记一些琐碎用例。