前端测试适合所有人

Avatar of Evgeny Klimenchenko
Evgeny Klimenchenko

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 价值 200 美元的免费积分!

测试是那些让你要么超级兴奋要么闭眼走开的事物之一。 无论你属于哪个阵营,我都想告诉你 **前端测试适合所有人**。 事实上,存在多种类型的测试,也许这就是一些人最初感到恐惧或困惑的原因。

我将在本文中介绍最流行和使用最广泛的测试类型。 对你们中的一些人来说,这可能没什么新鲜事,但至少可以作为复习。 无论如何,我的目标是让你对各种测试类型有一个很好的了解。 单元测试。 集成测试。 无障碍测试。 视觉回归测试。 这些是我们将共同探讨的内容。

不仅如此! 我们还将指出用于每种测试类型的库和框架,例如 Mocha、Jest、Puppeteer 和 Cypress 等。 而且不用担心——我会避免使用大量技术术语。 也就是说,你应该具有一些前端开发经验才能理解我们将介绍的示例。

好了,让我们开始吧!

什么是测试?

软件测试是对软件产品或服务进行的调查,以向利益相关者提供有关其质量的信息。

Cem Kaner,“探索性测试”(2006 年 11 月 17 日)

从最基本的角度来看,测试是一种自动化工具,可以尽早发现开发中的错误。 这样,你就可以在错误进入生产环境之前修复它们。 测试还可以提醒你可能忘记检查某个特定区域(例如无障碍性)的工作。

简而言之,前端测试验证了人们在网站上看到的内容以及他们在网站上使用的功能是否按预期工作

前端测试用于应用程序的客户端。 例如,前端测试可以验证按下“删除”按钮是否确实从屏幕上删除了项目。 但是,它不一定检查该项目是否已从数据库中实际删除——这种事情将在后端测试期间进行检查。

这就是测试的要点:我们希望在代码部署之前捕获客户端的错误并修复它们。

不同的测试着眼于项目的不同部分

不同类型的测试涵盖了项目的不同方面。 尽管如此,区分它们并了解每种类型的作用非常重要。 混淆哪些测试做什么会导致混乱、不可靠的测试套件。

理想情况下,你会使用几种不同类型的测试来发现不同类型的潜在问题。 一些测试类型具有 **测试覆盖率** 分析,可以显示该特定测试查看了多少代码(以百分比表示)。 这是一个很好的功能,虽然我见过开发人员的目标是 100% 的覆盖率,但我不会只依赖该指标。 最重要的是确保覆盖所有可能的边缘情况并将其考虑在内。

因此,让我们把注意力转向不同类型的测试。 请记住,并非你必须使用这些测试中的每一个。 重点在于能够区分这些测试,以便你了解在特定情况下使用哪些测试。

单元测试

单元测试是测试最基本的构建块。 它查看单个组件,并确保它们按预期工作。 这种测试对于任何前端应用程序都至关重要,因为有了它,你的组件会针对其预期行为进行测试,从而导致更可靠的代码库和应用程序。 这也是可以考虑和覆盖边缘情况的地方。

单元测试特别适合测试 API。 但是,与其调用实时 API,不如使用硬编码(或“模拟”)数据来确保你的测试运行始终一致。

让我们以一个非常简单(和原始)的函数为例

const sayHello = (name) => {
  if (!name) {
    return "Hello human!";
  }

  return `Hello ${name}!`;
};

同样,这是一个基本情况,但你可以看到它涵盖了一个小边缘情况,即有人可能忘记向应用程序提供名字。 如果存在 name,我们将获得“Hello ${name}!”,其中 ${name} 是我们希望用户提供的。

“嗯,为什么我们需要针对这样的小事情进行测试?” 你可能会想。 有几个非常重要的原因

  • 它迫使你深入思考函数的可能结果。 更常见的情况是,你确实会发现边缘情况,这有助于你将它们包含在代码中。
  • 代码的某些部分可能依赖于此边缘情况,如果有人来删除重要的东西,测试会提醒他们这段代码很重要,不能删除。

单元测试通常很小且简单。 以下是一个示例

describe("sayHello function", () => {
  it("should return the proper greeting when a user doesn't pass a name", () => {
    expect(sayHello()).toEqual("Hello human!")
  })

  it("should return the proper greeting with the name passed", () => {
    expect(sayHello("Evgeny")).toEqual("Hello Evgeny!")
  })
})

describeit 只是语法糖。 最重要的行带有 expecttoEqualdescribeit 将测试分解成逻辑块,这些块将打印到终端。 expect 函数接受我们要验证的输入,而 toEqual 接受所需的输出。 你可以使用许多不同的函数和方法来测试你的应用程序。

假设我们使用的是 Jest,这是一个用于编写单元的库。 在上面的示例中,Jest 会在终端中显示 sayHello 函数作为标题。 it 函数内部的所有内容都被视为单个测试,并在终端中显示在函数标题下方,使一切易于阅读。

绿色的勾号表示我们的两个测试都已通过。 太棒了!

集成测试

如果单元测试检查块的行为,集成测试则确保这些块完美地协同工作。 这使得集成测试非常重要,因为它开启了对组件之间交互的测试。 应用程序很少(如果有的话)由独立运行的孤立部分组成。 这就是我们依靠集成测试的原因。

我们回到我们进行过单元测试的函数,但这次在简单的 React 应用程序中使用它。 假设单击一个按钮会触发一个问候语出现在屏幕上。 这意味着测试不仅涉及该函数,还涉及 HTML DOM 和按钮的功能。 我们希望测试所有这些部分如何协同工作。

以下是我们要测试的 <Greeting /> 组件的代码

export const Greeting = () => {  
  const [showGreeting, setShowGreeting] = useState(false);  

 return (  
   <div>  
     <p data-testid="greeting">{showGreeting && sayHello()}</p>  
     <button data-testid="show-greeting-button" onClick={() => setShowGreeting(true)}>Show Greeting</button>  
   </div>
 );  
};

以下是集成测试

describe('<Greeting />', () => {  
  it('shows correct greeting', () => {  
    const screen = render(<Greeting />);  
     const greeting = screen.getByTestId('greeting');  
     const button = screen.getByTestId('show-greeting-button');  

     expect(greeting.textContent).toBe('');  
     fireEvent.click(button);  
     expect(greeting.textContent).toBe('Hello human!');  
 });  
});

我们已经从单元测试中了解了 describeit。 它们将测试分解成逻辑部分。 我们有 render 函数,它在模拟的 DOM 中显示 <Greeting /> 组件,以便我们可以测试与组件的交互,而无需触碰真实的 DOM——否则,这将非常昂贵。

接下来,测试通过测试 ID(分别为 #greeting#show-greeting-button)查询 <p><button> 元素。 我们使用测试 ID,因为从模拟的 DOM 中获取我们想要的组件更容易。 还有一些其他方法可以查询组件,但这是我最常使用的方法。

直到第 7 行才开始真正的集成测试! 我们首先检查 <p> 标签是否为空。 然后我们通过模拟 click 事件来单击按钮。 最后,我们检查 <p> 标签是否包含“Hello human!”。 就这样! 我们测试的只是在单击按钮后,一个空段落是否包含文本。 我们的组件已覆盖。

当然,我们可以添加输入,让用户输入他们的名字,并在问候语函数中使用该输入。 但是,我决定让它更简单。 当我们介绍其他类型的测试时,我们会使用输入。

查看运行集成测试时终端中得到的结果。

Termain message showing a passed test like before, but now with a specific test item for showing the correct greeting. It includes the number of tests that ran, how many passed, how many snapshots were taken, and how much time the tests took, which was 1.085 seconds.
完美!当点击按钮时,<Greeting /> 组件显示了正确的问候语。

端到端 (E2E) 测试

  • 级别:
  • 范围:通过向真实浏览器提供操作指令和预期结果,测试用户在真实浏览器中的交互。
  • 可能的工具:CypressPuppeteer

E2E 测试是列表中最高级别的测试。E2E 测试只关心人们如何看待您的应用程序以及他们如何与之交互。他们对代码和实现一无所知。

E2E 测试告诉浏览器做什么,点击什么以及输入什么。我们可以创建各种交互来测试不同的功能和流程,就像最终用户体验它们一样。它实际上是一个机器人,它与应用程序进行交互,点击应用程序,以确保一切正常。

E2E 测试在某种程度上类似于集成测试。但是,E2E 测试是在带有真实 DOM 的真实浏览器中执行的,而不是我们模拟出来的东西——我们通常使用真实数据和真实 API 进行这些测试。

最好使用单元测试和集成测试来实现完全覆盖。但是,当用户在浏览器中运行应用程序时,可能会遇到意外的行为——E2E 测试是解决此问题的完美方案。

让我们看一个使用 Cypress 的示例,它是一个非常流行的测试库。我们将专门使用它对我们之前的组件进行 E2E 测试,这次是在带有额外功能的浏览器中。

同样,我们不需要看到应用程序的代码。我们假设我们有一些应用程序,并且希望像用户一样对其进行测试。我们知道要点击哪些按钮以及这些按钮的 ID。这实际上是我们唯一需要关注的。

describe('Greetings functionality', () => {  
  it('should navigate to greetings page and confirm it works', () => {
    cy.visit('http://localhost:3000')  
    cy.get('#greeting-nav-button').click()  
    cy.get('#greetings-input').type('Evgeny', { delay: 400 })  
    cy.get('#greetings-show-button').click()  
    cy.get('#greeting-text').should('include.text', 'Hello Evgeny!')  
  })  
})

这个 E2E 测试看起来与我们之前的集成测试非常相似。命令非常相似,主要区别在于这些命令是在真实浏览器中执行的。

首先,我们使用 cy.visit 导航到应用程序所在的特定 URL。

cy.visit('http://localhost:3000')

其次,我们使用 cy.get 通过 ID 获取导航按钮,然后指示测试点击它。该操作将导航到包含 <Greetings /> 组件的页面。事实上,我已经将该组件添加到了我的个人网站,并为其提供了自己的 URL 路由。

cy.get('#greeting-nav-button').click()

然后,依次获取文本输入框,输入“Evgeny”,点击 #greetings-show-button 按钮,最后检查我们是否获得了预期的问候语输出。

cy.get('#greetings-input').type('Evgeny', { delay: 400 })
cy.get('#greetings-show-button').click()
cy.get('#greeting-text').should('include.text', 'Hello Evgeny!')  

观看测试如何在真实浏览器中为您点击按钮真的很酷。我稍微放慢了测试速度,以便您可以看到发生了什么。所有这些通常发生得非常快。

以下是终端输出

Terminal showing a run test for greetings.spec.js that passed in 12 seconds.

可访问性测试

Web 可访问性是指网站、工具和技术的设计和开发,以便残疾人能够使用它们。

W3C

可访问性测试确保残疾人能够有效地访问和使用网站。这些测试验证您是否遵循以可访问性为中心的网站构建标准。

例如,许多视力障碍者使用屏幕阅读器。屏幕阅读器会扫描您的网站,并尝试以残疾用户能够理解的格式(通常是口语)向他们展示网站。作为开发人员,您希望使屏幕阅读器的工作更轻松,而可访问性测试将帮助您了解从哪里开始。

有很多不同的工具,有些是自动化的,有些是手动运行的,用于验证可访问性。例如,Chrome 已经将一个工具内置到其 DevTools 中。您可能知道它是 Lighthouse

让我们使用 Lighthouse 来验证我们在 E2E 测试部分创建的应用程序。我们在 Chrome DevTools 中打开 Lighthouse,点击“可访问性”测试选项,然后“生成”报告。

这就是我们要做的全部!Lighthouse 会完成它的工作,然后生成一个很棒的报告,包括分数、运行的审核摘要以及改进分数的机会概述。

但这只是一个从其特定角度衡量可访问性的工具。我们有 各种可访问性工具,值得制定一个计划,说明要测试什么以及可用于解决这些问题的工具。

视觉回归测试

  • 级别:
  • 范围:测试应用程序的视觉结构,包括代码更改产生的视觉差异。
  • 可能的工具:CypressPercyApplitools

有时,E2E 测试不足以验证对应用程序的最新更改是否没有破坏界面中任何内容的视觉外观。您是否将代码更改推送到生产环境中,只是发现它破坏了应用程序其他部分的布局?好吧,您并不孤单。大多数情况下,代码库的更改会破坏应用程序的视觉结构或布局。

解决方案是视觉回归测试。它的工作原理非常简单。视觉测试只需对页面或组件进行截图,并将其与之前成功测试中捕获的截图进行比较。如果这些测试发现截图之间有任何差异,它们就会向我们发出某种通知。

让我们转向一个名为 Percy 的视觉回归工具,看看视觉回归测试是如何工作的。还有很多其他方法可以进行视觉回归测试,但我认为 Percy 很容易演示。事实上,您可以跳到 Paul Ryan 关于 Percy 在 CSS-Tricks 上的深入探讨。但我们将做一些更简单的事情来说明这个概念。

我故意通过将按钮移到输入框的底部来破坏问候语应用程序的布局。让我们尝试使用 Percy 来捕获此错误。

Percy 与 Cypress 结合使用效果很好,因此我们可以按照他们的 安装指南,与现有的 E2E 测试一起运行 Percy 回归测试。

describe('Greetings functionality', () => {  
  it('should navigate to greetings page and confirm everything is there', () => {  
    cy.visit('http://localhost:3000')  
    cy.get('#greeting-nav-button').click()  
    cy.get('#greetings-input').type('Evgeny', { delay: 400 })  
    cy.get('#greetings-show-button').click()  
    cy.get('#greeting-text').should('include.text', 'Hello Evgeny!')  


    // Percy test
     cy.percySnapshot() // HIGHLIGHT
  })  
})

我们只是在 E2E 测试结束时添加了一行代码:cy.percySnapshot()。这将拍摄截图并将其发送到 Percy 进行比较。就是这样!测试完成后,我们会收到一个链接来检查我们的回归。以下是我在终端中获得的结果

Terminal output that shows white text on a black background. It displays the same result as before, but with a step showing that Percy created a build and where to view it.
嘿,看,我们可以看到 E2E 测试也通过了!这表明 E2E 测试并不总是能捕获视觉错误。

以下是我们从 Percy 获取的结果

Animated gif of a webpage showing a logo and navigation above a form field. The animation overlays the original snapshot with the latest to reveal differences between the two.
显然有些东西发生了变化,需要修复。

性能测试

性能测试非常适合检查应用程序的速度。如果性能对您的业务至关重要——鉴于 最近对核心 Web 指标和 SEO 的关注,您一定想知道代码库的更改是否对应用程序的速度产生了负面影响。

我们可以将其烘焙到我们其余的测试流程中,也可以手动运行它们。完全由您决定如何运行这些测试以及运行它们的频率。一些开发人员会创建所谓的 “性能预算”,并运行一个计算应用程序大小的测试——如果大小超过某个阈值,失败的测试将阻止部署发生。或者,使用 Lighthouse 定期手动测试,因为它也会测量性能指标。或者将两者结合起来,将 Lighthouse 集成到测试套件中

性能测试可以测量与性能相关的任何内容。它们可以测量应用程序加载的速度、初始包的大小,甚至特定函数的速度。性能测试是一个相当广泛、广阔的领域。

以下是一个使用 Lighthouse 的快速测试。我认为它很好地展示了它对核心 Web 指标的关注,以及它在 Chrome 的 DevTools 中无需任何安装或配置即可轻松访问的特性。

A Lighthouse report open in Chrome DevTools showing a Performance score of 55 indicated by a orange circle bearing the score. Various metrics are listed below the score, including a timeline of the page as it loads.
分数不太好,但至少我们可以看到发生了什么,并且有一些关于如何改进的建议。

总结

以下是我们涵盖内容的细分

类型级别范围工具示例
单元测试应用程序的函数和方法。
集成中等测试单元之间的交互。
端到端通过向真实浏览器提供操作指令和预期结果,测试用户在真实浏览器中的交互。
无障碍性根据无障碍标准测试应用程序的界面。
视觉回归测试应用程序的视觉结构,包括代码更改产生的视觉差异。
性能测试应用程序的性能和稳定性。

那么,测试适合所有人吗?是的!鉴于我们拥有如此多的库、服务和工具来测试应用程序不同方面的不同点,至少有一些工具可以让我们根据标准和预期来衡量和测试代码——其中一些甚至不需要代码或配置!

根据我的经验,许多开发人员忽视了测试,认为简单的点击浏览或发布检查就能帮助他们发现代码更改中可能出现的任何错误。如果您想确保您的应用程序按预期运行,对尽可能多的人群具有包容性,高效运行并设计良好,那么测试需要成为您的工作流程的核心部分,无论是自动的还是手动的。

既然您已经了解了各种测试类型以及它们的工作原理,您将如何将测试实施到您的工作中呢?