skip to content
usubeni fantasy logo Usubeni Fantasy

前端测试教程——自动化测试的类型

/ 16 min read

原文传送门 Types of automated testing

在各种代码库中,对不同类型的测试进行命名通常遵循一定的共性,尽管这些命名并没有严格的定义标准。本课程尝试对每种测试类型给出了一些建议和解释,但你可能在其他地方看到有所不同的定义。

之前的内容中提到了单元测试和组件测试(以我们讨论的 React 组件为例)。这两种测试类型在我们的测试金字塔(或其他形状的模型)中处于较低位置,原因在于它们相对简单、执行速度快。然而,相较于更为复杂的集成测试,它们的实用性可能就不那么显著了。

测试的类型

单元测试

单元测试关注的范畴是最细微的。它们倾向于针对代码的某些小片段进行测试,或者测试绝对无状态的代码,这几乎就像解数学题一样:假设向你的代码提供了输入 X、Y 和 Z,那么它应当返回输出 A、B 和 C。

具备单元测试的代码通常不涉及外部依赖,比如从网络上拉取数据或者隐式地调用其他函数或库。可以把它看作是代码中的一个节点,你可以把它单独拿出来进行测试。

尽管单元测试编写起来简单且执行迅速,但要注意,测试代码的这些微小单元并不总能提供我们真正需要的信息。往往,这些代码单元缺乏与其他部分的互动,实际上我们可能需要在更宏观的层面上进行测试,以此来减少潜在的风险。

组件测试

对于网页开发者而言,“组件”这一术语有着多重含义,它既可以指用户能够直接看到的界面元素,如 React 组件或 Web 组件,也可以泛指任何一个可以单独测试的工作单元,比如带有外部依赖的类。为了确保这样的组件能被有效地测试,我们必须对其依赖进行模拟或者直接忽略。

在现代的网页开发实践中,由于整个开发过程都围绕着组件的概念展开,因此将测试思维定位在组件测试上是一种非常实际的方法。举个例子,你可能会规定每个组件都必须通过特定的测试。这种组件测试方法在某个组件明确归属于单个开发者或者小团队管理的场景中尤其高效。但是,当面对复杂的依赖关系时,进行模拟可能会变得相当棘手。

集成测试

这类测试主要针对代码中的一小批组件、模块、子系统或其他重要部分,以确保它们能够协同工作。这个定义比较泛泛。对网页开发人员而言,可以将其理解为:你所测试的代码并非网站的实际生产版本(甚至相差甚远),但它依然能够将系统中相关的各个部分连接起来。

这也可能涉及到“真实”的依赖关系,比如使用处于测试模式的外部数据库,而不仅仅是进行简单模拟。举个例子,我们不是假设 query() 函数总会返回同样的两条记录,而是通过集成测试来验证测试用的数据库里确实存有数据。虽然具体的数据内容不是重点,关键在于验证系统是否能够成功地连接并查询数据库。

通过编写相对简单的集成测试,并利用断言来验证单一操作在不同组件间可能触发的一连串可测效果,我们能够有效地确认复杂系统的运作是否符合预期。然而,这类测试的编写和维护过程可能相当复杂,且容易引入不必要的复杂性。比如说,为集成测试创建一个 FakeUserService 就需要保证它和真实的 RealUserService 同样需要实现 UserService 接口,这无疑增加了额外的负担。

冒烟测试

冒烟测试是一些快速而简单的检查,旨在确认你的代码库是否运行正常。这通常涉及对那些对用户体验有重大影响的代码进行初步测试。

以一个大型的网络应用为例,一个关键的冒烟测试可能是验证登录和认证系统是否正常工作。如果这个系统出现故障,那么应用程序将无法使用,其他的测试也就失去了意义。

在庞大的代码库中,冒烟测试是非常适合作为 package.json 中 test 脚本的一部分来执行的。除了自动化测试,手动进行的测试也能充当冒烟测试的角色。

回归测试

回归测试是一种特殊的烟雾测试,它确保在发布新版本或开发新功能后,现有功能仍能正常工作,同时避免以前的错误再次出现。

这一概念与测试驱动开发(TDD)紧密相关。专门编写的测试用例用于触发特定错误,并在后续确保该错误得到修复,这些用例就构成了回归测试用例,因为它们能防止同一个错误再度发生。

但是,回归测试存在一个难题,而且并没有一个完美的解决方案。这是商业开发中经常提到的一个概念:随着新功能的增加,确保原有功能不受影响至关重要。理论上,一个经过充分测试的代码库应该能够做到这一点,但实际上往往达不到这个标准。我们将在后续章节中更详细地探讨这个问题。

视觉测试

视觉测试是指捕捉网站当前状态的截图或视频,然后将其与已知的正常状态(比如,之前的截图)进行比较来检查。这种测试本质上需要启动一个实际的浏览器,这样才能渲染 HTML、CSS 和网站的其他内容。

相比于覆盖完整代码库的端到端视觉测试,一个更有用的做法是选用一些现成的测试框架(Cypress 等),它们只渲染特定的组件,尤其是为了适应不同的屏幕尺寸而触发响应式用户界面的情况。这要比单纯使用 JSDOM 或者类似的框架要复杂得多。

视觉测试的失败通常是其他类型错误的重要预兆。然而,复杂的用户界面可能会因为一些与测试目标特性无关的因素而未通过视觉测试,比如新加入的功能改变了用户界面的外观,或者新的操作系统版本对表情符号的呈现方式与旧版本不同。

每当测试对象的代码发生变化就需要更新的测试,通常被认为过于刻板,因为它们无法提供实质性的反馈——它们的失败似乎是不可避免的。视觉测试尤其容易遇到这种问题。我们在探讨测试的哲学时,将深入讨论这个问题。

端到端测试

端到端测试位于测试金字塔的顶层,它全面检验了用户与网页应用或网站的互动体验,通常聚焦于某个特定功能。这类测试一般在一个由 WebdriverIO、Selenium 或 Puppeteer 等工具控制的浏览器中进行,模拟代码库在生产环境中的实际运行情况(虽然通常是在本地主机上执行)。

这种关测试可能包括以测试用户的身份登录、进行关键操作,并验证网站或系统是否运行正常。我们后续还会详细介绍更多这类型测试的例子。尽管端到端测试具有很强的能力,但有时候它们的维护可能相当复杂。

简化测试的一些方法包括缩小测试的范围或在适当的情况下模拟某些组件。举个例子,假设用户需要登录您的网站,但测试的重点并非登录功能,您可以在测试环境中设置一个特殊标志,使得测试控制器能够代替用户进行操作,而无需实际登录或生成相应的 cookies。

虽然端到端测试是检验代码库大部分区域的有力手段,但这种大范围的测试因为依赖外部系统,可能会导致测试结果不稳定或不可信。此外,如果每次测试都会创建或更改数据库条目,那么这些测试往往会在数据库中留下大量测试数据。这样的数据残留可能会使得分析测试失败的原因变得更加复杂。

API 测试

API 测试涵盖了两个方面:一是验证自己软件提供的 API 行为是否符合预期,二是访问真实世界中的(可能是实时的)API,以检验它们的表现。这类测试主要是为了检查不同系统之间的交互接口——即它们最终如何互相通信,而并不真正将它们集成在一起,这一点与集成测试有所不同。

这种测试方式能够在不需承担运行测试中所涉及系统的额外负担的情况下,作为进入集成测试的一个基础步骤。但是,对于那些真实世界的系统测试,可能会面临测试结果不稳定的问题。

其他类型

在软件测试领域,根据不同的需求和场景,我们可以采用多种不同的测试方法。其中一些特别有趣的例子包括:

  • 手动测试:这是一种基本的测试方法,依赖于测试人员手动执行测试来发现问题。
  • 验收测试:作为一种手动测试形式,它特别受到敏捷开发方法的青睐,目的是验证产品是否满足用户的实际需求。
  • 混沌测试:这种测试通过输入随机数据来观察系统反应,目的是确保系统能够在遭遇异常数据时保持稳定,不会轻易崩溃。
  • 失败测试:这种测试通过模拟网络故障等复杂系统的各种失败情况,来验证系统是否能够在出现故障时以一种可控和预期的方式响应。
  • 构建测试:这种测试验证是否能够成功生成代码库的构建产物,例如通过检查这些产物是否存在或它们包含的内容,来确保复杂的内容管理系统(CMS)的输出符合预期。

代码覆盖率

我们可以计算代码被自动化测试覆盖的比例,并随着时间的推移对这个统计数字进行跟踪。但是,我们并不建议追求 100% 的测试覆盖率,因为这可能会带来不必要的额外工作量,并且可能产生一些只是形式上的、设计不足的测试,这些测试并不能深入检查到关键功能。

在编写或进行测试工作时,尤其是集成测试,测试覆盖率本身也是一个非常有价值的工具。通过展示哪些代码被某个具体测试覆盖的百分比或具体到每一行的详细情况,你可以更清楚地了解哪些部分还没有被测试到,或者接下来应该进行哪些测试。

阅读资料

评论组件加载中……