有多少测试才算足够?

乔梁 | 2021-06-15

每一个软件开发人员和团队都会遇到一个熟悉的问题:“有多少测试足以证明一个软件版本是合格的?”这在很大程度上取决于软件的类型、用途和目标受众。有人可能会期望使用一种比测试智能手机手电筒应用程序更为严格的方法来测试商业搜索引擎。然而,无论应用程序是什么,「需要多少测试才是足够的?」这一问题可能很难用明确的术语来回答。更好的方法是提供考虑因素或经验法则,用于定义最适合当前情况的鉴定过程和测试策略。以下提示提供了一个有用的评估标准:

  • 记录你的过程或策略
  • 有坚实的单元测试基础。
  • 不要吝啬于集成测试。
  • 对关键用户旅程执行端到端测试。
  • 理解并实施其他层次的测试。
  • 了解您对代码和功能的覆盖范围。
  • 利用现场反馈改进流程。

记录你的过程或策略

如果你正在测试你的产品,请记录整个测试过程。这一点对于今后版本的重复测试和为进一步改进所做的分析是非常重要的。如果这是你的第一个产品版本,最好有一个书面的测试计划或策略。事实上,书面测试计划或策略应该伴随着任何产品的设计。

有坚实的单元测试基础

一个很好的开始就是编写代码时附带单元测试。单元测试是对功能单元级别的代码进行测试。如果有对外部服务的依赖,要么是使用 Mock ,要么使用 Fake 。

Mock 与生产依赖项具有相同的接口,但只检查对象是否根据设置的期望使用和(或)返回测试控制的值,而不是完全实现其正常功能。

另一方面,Fake 是依赖关系的浅层实现,但理想情况下它自己应该没有自己的依赖关系。Fake 比 Mock 提供了更广泛的功能,并且它应该由提供依赖项生产版本的团队来维护。这样一来,随着依赖关系的发展,Fake 代码也会演变。那么,单元测试的编写者就可以信赖其所用的 Fake 真实地反映了生产依赖关系的功能。

在很多公司,包括 Google ,都要求「任何代码变更都要有相应的单元测试用例通过」这一最佳实践。随着代码库的扩大,在提交代码之前执行一系列这样的测试是在 bug 带入代码库之前能捕获它的一个重要措施。这节省了以后编写集成测试、调试和验证对现有代码的修复的时间。

不要吝啬集成测试

随着代码库的增长,一定会达到一个点,即:功能的数量达到了可以作为一组进行测试的地步,这时就应该有一个坚实的集成测试基础了。集成测试是将一小群单元(通常只有两个功能单元)作为一个整体来测试它们的行为,以验证它们是否一致地协同工作。

开发人员常常认为可以降低编写这种子功能的集成测试的优先级,甚至可以跳过它,直接使用完整的端到端测试。毕竟,后者是象用户真正使用地方式那样来测试你的产品。然而,拥有一套全面的集成测试与拥有一个坚实的单元测试基础一样重要(请参阅之前的Google博客文章——修复测试沙漏)。

原因也非常简单。与完整的端到端测试相比,集成测试的依赖关系更少,更简单。所以,它可以在更小的环境中执行,也就比完整的端到端测试和其全套依赖性更快、更可靠(参见之前的Google博客文章 flaky testing

对关键地用户旅程(Critical User Journeys)执行端到端的测试

到目前为止的讨论涵盖了产品测试中的组件层级的测试,首先是单个组件的单元测试,然后是多组件及其依赖项的集成测试。现在是时候对产品进行端到端测试了,也就是用户使用测试。这一点非常重要。因为我们应该测试的不仅仅是独立的特性,还要包含各种特性组合的整个工作流。在谷歌,这些工作流程——关键目标以及用户为实现该目标所要执行的任务旅程的结合——被称为关键用户旅程(critical user Journeys,CUJs)。理解cuj,记录它们,然后使用端到端测试(希望是以自动化的方式)验证它们,就完成了测试金字塔。

理解并实现其它类型的测试

单元测试、集成测试和端到端测试解决了产品功能性验证。而了解其他类型的测试也很重要,包括:

  • 性能测试( Performance testing ) —衡量应用程序或服务的延迟或吞吐量。
  • 负载和可伸缩性测试( Load and scalability testing )—在越来越高的负载下测试应用程序或服务。
  • 容错测试( Fault-tolerance testing )——当不同的依赖项失败或完全失效时测试应用程序的行为。
  • 安全测试( Security testing )—测试服务或应用程序中的已知漏洞。
  • 可访问性测试( Accessibility testing )——确保产品对每个人都是可访问和可用的,包括各种残疾人士。
  • 本地化测试( Localization testing )—确保产品可以在特定的语言或地区使用。
  • 全球化测试( Globalization testing )-确保产品能被全世界的人使用。
  • 隐私测试( Privacy testing )—评估和减轻产品中的隐私风险。
  • 易用性测试( Usability testing )—测试用户友好性

同样,让这些测试过程在您的评审周期中尽早发生是很重要的。较小的性能测试可以更早地检测回归问题,并在端到端测试期间节省调试时间。

理解代码和功能的覆盖率

到目前为止,从定性的角度来说,「有多少测试才算足够」这个问题已经得到了解答。我们列举了不同类型的测试,并提出了论点,即:较小和较早是优于较大和较晚。

现在让我们从定量的角度来研究这个问题,并考虑代码覆盖技术。

关于代码覆盖率,Wikipedia上有一篇不错的文章「代码覆盖率」,说明并讨论了各种类型的覆盖率,包括语句(statement), 边界(edge), 分支( branch)和 条件覆盖。还有很多开源工具,供不同编程语言所使用。下表中列举了其中的一部分:

LanguageTool
JavaJaCoCo
JavaJCov
JavaOpenClover
PythonCoverage.py
C++Bullseye
GoBuilt in coverage support (go -cover)

这些工具中的大多数都以百分比形式提供摘要。例如,80%的代码覆盖率意味着大约80%的代码被覆盖,大约20%的代码被覆盖。同时,也需要理解下面这一点:你虽然已经覆盖了某个特定的代码区域,但这段代码仍然可能有 bug

覆盖率中的另一个概念是 变更覆盖率(也叫 Changelist Coverage )。变更覆盖率衡量的是被修改或新增代码行的覆盖率。对于那些积累了技术债务并且在整个代码库中覆盖率较低的团队来说,这个指标非常有用。这些团队可以制定一个策略,提高他们的增量覆盖率,从而促进整体改善。

到目前为止,覆盖率的讨论主要是围绕对函数或行的测试覆盖率展开。另一种类型的覆盖率是特性覆盖或行为覆盖。对于特性覆盖率,重点是在于识别某个版本中所包含的特性,并为该特性编写测试。对于行为覆盖,重点是识别 CUJ ,并创建适当的测试来跟踪它们。再次强调,了解你有多少“未发现”的特征和行为,可以作为你了解风险的有用指标。

利用现场反馈(field feedback)来改进流程

了解和改进质检过程的一个非常重要的部分是在软件发布后从现场收到的反馈。拥有一个完整的流程,以改进质检的行动项的形式,可以跟踪中断、bug和其他问题,这对于最小化后续版本的回归风险是至关重要的。

此外,行动项应确保:(1)强调在质检过程中尽早填补测试缺口;(2)解决战略问题,如缺乏特定类型的测试,如负载或容错测试。同样,这也是为什么记录你的质检过程很重要的原因,这样你就可以根据从现场获得的数据重新评估它。

小结

创建一个全面的质检过程和测试策略,来回答“多少测试就足够了?”的问题也许是一项复杂的任务。希望这里给出的提示能对你有所帮助。总而言之:

  • 记录你的过程或策略
  • 有坚实的单元测试基础。
  • 不要吝啬于集成测试。
  • 对关键用户旅程执行端到端测试。
  • 理解并实施其他层次的测试。
  • 了解您对代码和功能的覆盖范围。
  • 利用现场反馈改进流程

原文作者:Adel Saoud

原文链接:Google Testing Blog: How Much Testing is Enough?

发表时间:June 15, 2021