往期内容:
(一)应该测试的是行为,不是方法
(二)端到端的测试,到底应该测什么?
(三)如何正确使用测试替身对象?
(四)如何写出稳定运行的测试用例?
本期要点:
1. 测试方法的命名应该具备描述性
2. 应该测试 Public API,而不是细节的实现方式
更多内容请见谷歌测试博客
https://testing.googleblog.com/
∎ 1 ∎
测试方法名应该有描述性
要想了解下面这段测试代码在验证什么行为,你需要花多长时间?
你可能要从头到尾读完每一行代码才能知道每一行代码在做什么,
甚至还要读上两遍。
但是,
假如我们把这个测试用例名改成下面的样子,你会花多长时间呢?
现在,你只需要读一下测试用例的名字,就应该知道被测试的行为是什么了,甚至不需要读测试代码。
在第一个代码示例中的测试用例名中虽然有一个提示, `invalidLogin` ,但它并没有真正地告诉你,我们期望的结果到底是什么,所以,你不得不读完所有的代码才能知道。
在测试用例名字中包含将测试场景和期望的结果还有几个其它的好处:
如果你想了解某个类所有可能的行为,你所需要做的只是在它对应的那些测试用例类的测试方法名,而不是花数十分钟或者几个小时反复翻看测试代码,甚至生产代码本身,才能知道它的行为。在 Code Review 的时候,这种做法也会很有用,因为你在 Review 这段代码时,就会很快知道这些测试是否覆盖了所有的用例。
通过给测试用例一个更加明确的名称,会迫使你将测试不同的行为拆分到单独的测试用例中。否则,您可能会尝试将不同行为的断言都放在一个测试用例中。而随着时间的推移,这可能导致测试用例不断增长,变得难以理解和维护。
在某些情况下,我们很可能无法通过测试代码本身非常清晰地就表达打算测试哪一个确切行为。此时,如果测试名称没有明确说明这一点,你很可能要去猜测测试实际上在测什么。
你能很容易地判断出,是否还有哪些功能没有被测试到。比如,当你没发现那个描述你正在寻找的代码行为的测试名时,你就知道代码的作者没有写全测试。
当测试失败时,你能立即看到哪个功能被破坏了。
有几种常见的测试名称的命名结构模式,其中一种就是将测试名写成一个句子,并在名称中包含关键字 “should” ,例如:
shouldLockOutUserAfterThreeInvalidLoginAttempts
无论你用什么模式,有个建议是通用的:确保测试用例名字中包括被测试的场景和期望的输出。
当然,有时只是指定被测方法的名字就足够了,特别是当这个方法非常简单,并且只有一个行为,而这个行为就是这个方法名本身。
∎ 2 ∎
应该测试 Public API,而不是细节
下面的这个类需要测试么?
它的 Validate()
方法是包含一些业务逻辑的,也许应该对它进行测试。
但是, 如果它的调用者是以下面的方式来使用它的呢?
答案可能是:它也许不需要测试 ,因为所有的路径都可以通过 UserInfoService
被测试到。关键点在于:这个类是一种实现细节,而不是Public API。
Public API 可能被任何调用者所使用,而调用者可能会将从任意组合的参数引入到该方法中。你希望所有可能性都被测试覆盖, 从而在调用者使用这个 API 时,不会发生任何问题。
Public API 包括两类:
在代码仓库中其它地方的代码调用的类。例如,一个服务类,会被客户端代码所调用。
公共的工具类,可能被整个代码仓库所用。
一个被称为"实现细节"的类,其存在的意义就是为了支持某个公共API,并且只有有限个数的调用者(通常只有一个)。有时候,这些类可能通过测试那些调用它们的API而被间接测试到。
在有些情况下,测试这种细节实现类也是有用的,例如,如果这个类非常复杂,或者对公共API的一些测试用例非常难写。但当你做这些测试时,通常并不需要象测试公共API那样全面且深入,因为有一些看上去可能出现的输入参数根本不可能被传到这个方法中来( 例如,在上面的代码样例中,如果UserInfoService
已经确保 UserInfo
不可能为 null
,那么就没有必要测试当把null
作为参数传入 UserInfoValidator.validate
时会发生什么,因为这根本不可能发生)。
细节实现类有时可能被认为是私有方法(private methods)被放到了另外的一个类中了,因为你想遵守"不应该测试私有方法"的原则。你也应该严格限制这种细节实现类的可见性,例如,在Java中可能应该是在包的范围可见即可(package-private)。
测试细节实现类常常导致一些耦合问题:
由于你经常需要更新测试,所以代码很难维护,例如,当修改一个细节实现类的方法签名时,或者在进行一个重构操作时。如果只通过公共API来进行测试,那么这类变更就根本不会影响测试代码。
如果只通过细节实现类测试某个行为,你可能会对你的代码得到假信心(false confidence),因为当使用公共API时,同样的代码路径也许并不能按预期方式工作。你也不得不在重构时更加小心,因为,如果并不是所有的路径都能通过公共API被测试到,那么,就很难确保公共API的所有行为会得到预期结果。