Jest 快照测试

2021-09-18 20:16 更新

每当你想要确保你的UI不会有意外的改变,快照测试是非常有用的工具。

典型的快照测试用例呈现 UI 组件,拍摄快照,然后将其与存储在测试旁边的参考快照文件进行比较。如果两个快照不匹配,则测试将失败:更改是意外的,或者参考快照需要更新到新版本的 UI 组件。

使用 Jest 进行快照测试

在测试 React 组件时可以采用类似的方法。你可以使用测试渲染器为你的 React 树快速生成可序列化的值,而不是渲染需要构建整个应用程序的图形 UI。考虑Link组件的这个例子测试

  1. import React from 'react';
  2. import renderer from 'react-test-renderer';
  3. import Link from '../Link.react';
  4. it('renders correctly', () => {
  5. const tree = renderer
  6. .create(<Link page="http://www.facebook.com">Facebook</Link>)
  7. .toJSON();
  8. expect(tree).toMatchSnapshot();
  9. });

第一次运行此测试时,Jest 创建一个如下所示的快照文件

  1. exports[`renders correctly 1`] = `
  2. <a
  3. className="normal"
  4. href="http://www.facebook.com" rel="external nofollow" target="_blank"
  5. onMouseEnter={[Function]}
  6. onMouseLeave={[Function]}
  7. >
  8. Facebook
  9. </a>
  10. `;

快照工件应与代码更改一起提交,并作为代码审查过程的一部分进行审查。在代码审查期间,Jest 使用友好的格式使快照更易于阅读。在后续的测试运行中,Jest 会将渲染的输出与之前的快照进行比较。如果它们匹配,则测试将通过。如果它们不匹配,则测试运行程序在您的代码中发现了一个应该修复的错误(在本例中,它是​<Link>​组件),或者实现已更改并且快照需要更新。

 注意:快照直接作用于呈现的数据——在我们的实例中,它是由​<Link />​的组件 page prop 传递给它。这也意味着即使任何其他文件在​<Link />​组件中缺少 props (比如:​App.js​)。它仍然可以通过测试,因为测试不知道​<Link />​组件的用法,并且它的范围仅限于 ​Link.react.js​。此外,在其他快照测试中使用不同的 props 渲染相同的组件是不会影响第一个,因为测试不知道彼此。

有关快照测试如何工作以及我们为何构建它的更多信息,请参阅发布博客文章。我们建议你阅读这篇博文,以了解何时应该使用快照测试。我们还建议你观看有关使用 Jest 进行快照测试的蛋头视频

更新快照

在引入错误后,快照测试什么时候失败是很容易被发现。发生这种情况时,请继续解决问题并确保你的快照测试再次通过。现在,让我们谈谈快照测试由于有意的实现更改而失败的情况。

如果我们有意更改示例中的 Link 组件指向的地址,就会出现这样一种情况。

  1. // Updated test case with a Link to a different address
  2. it('renders correctly', () => {
  3. const tree = renderer
  4. .create(<Link page="http://www.instagram.com">Instagram</Link>)
  5. .toJSON();
  6. expect(tree).toMatchSnapshot();
  7. });

在这种情况下,Jest 将打印以下输出:

Snapshot Testing - 图1

由于我们刚刚更新了我们的组件以指向不同的地址,因此期望该组件的快照发生变化是合理的。我们的快照测试用例失败了,因为我们更新的组件的快照不再匹配这个测试用例的快照工件。

为了解决这个问题,我们需要更新我们的快照工件。你可以运行带有标志的 Jest,该标志会告诉它重新生成快照:

  1. jest --updateSnapshot

继续并通过运行上述命令接受更改。​-u​如果您愿意,您也可以使用等效的单字符标志来重新生成快照。这将为所有失败的快照测试重新生成快照工件。如果由于意外错误导致我们有任何其他失败的快照测试,我们需要在重新生成快照之前修复错误,以避免记录错误行为的快照。

如果你想限制重新生成哪些快照测试用例,可以传递一个额外的​--testNamePattern​标志来仅为那些与模式匹配的测试重新记录快照。

你可以通过克隆快照示例、修改​Link​组件和运行 Jest来尝试此功能。

交互式快照模式

失败的快照也可以在监视模式下交互更新:

Snapshot Testing - 图2

一旦你进入交互式快照模式,Jest 将一次一次地引导你完成失败的快照,并让你有机会查看失败的输出。

从这里你可以选择更新该快照或跳到下一个:

Snapshot Testing - 图3

完成后,Jest 会在返回观看模式之前为你提供摘要:

Snapshot Testing - 图4

内联快照

内联快照的行为与外部快照(​.snap​文件)相同,只是快照值会自动写回到源代码中。这意味着可以获得自动生成快照的好处,而无需切换到外部文件以确保写入了正确的值。

内联快照由Prettier提供支持。要使用内联快照,您必须已​prettier​在项目中安装。写入测试文件时,您的 Prettier 配置将得到尊重。
如果您​prettier​安装在 Jest 找不到的位置,您可以使用"prettierPath"配置属性告诉 Jest 如何找到它。

示例:

首先,编写一个测试,​.toMatchInlineSnapshot()​不带参数调用:

  1. it('renders correctly', () => {
  2. const tree = renderer
  3. .create(<Link page="https://prettier.io">Prettier</Link>)
  4. .toJSON();
  5. expect(tree).toMatchInlineSnapshot();
  6. });

下次运行 Jest 时,​tree​将进行评估,并将快照作为参数写入​toMatchInlineSnapshot​:

  1. it('renders correctly', () => {
  2. const tree = renderer
  3. .create(<Link page="https://prettier.io">Prettier</Link>)
  4. .toJSON();
  5. expect(tree).toMatchInlineSnapshot(`
  6. <a
  7. className="normal"
  8. href="https://prettier.io" rel="external nofollow" target="_blank"
  9. onMouseEnter={[Function]}
  10. onMouseLeave={[Function]}
  11. >
  12. Prettier
  13. </a>
  14. `);
  15. });

这里的所有都是它的!甚至可以​--updateSnapshot​使用模式​u​键或使用键来更新快照​--watch​。

属性匹配器

对象中通常有您想要生成快照的字段(如 ID 和日期)。如果你想尝试对这些对象进行快照,它们将强制快照在每次运行时失败:

  1. it('will fail every time', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot();
  8. });
  9. // Snapshot
  10. exports[`will fail every time 1`] = `
  11. Object {
  12. "createdAt": 2018-05-19T23:36:09.816Z,
  13. "id": 3,
  14. "name": "LeBron James",
  15. }
  16. `;

对于这些情况,Jest 允许为任何属性提供非对称匹配器。在写入或测试快照之前检查这些匹配器,然后将其保存到快照文件而不是接收到的值:

  1. it('will check the matchers and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot({
  8. createdAt: expect.any(Date),
  9. id: expect.any(Number),
  10. });
  11. });
  12. // Snapshot
  13. exports[`will check the matchers and pass 1`] = `
  14. Object {
  15. "createdAt": Any<Date>,
  16. "id": Any<Number>,
  17. "name": "LeBron James",
  18. }
  19. `;

任何不是匹配器的给定值都将被准确检查并保存到快照中:

  1. it('will check the values and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. name: 'Bond... James Bond',
  5. };
  6. expect(user).toMatchSnapshot({
  7. createdAt: expect.any(Date),
  8. name: 'Bond... James Bond',
  9. });
  10. });
  11. // Snapshot
  12. exports[`will check the values and pass 1`] = `
  13. Object {
  14. "createdAt": Any<Date>,
  15. "name": 'Bond... James Bond',
  16. }
  17. `;

最佳实践

快照是识别应用程序中意外界面更改的绝佳工具——无论该界面是 API 响应、UI、日志还是错误消息。与任何测试策略一样,为了有效地使用它们,你应该了解一些最佳实践以及应该遵循的指南。

1. 将快照视为代码

提交快照并作为常规代码审查过程的一部分进行审查。这意味着像对待项目中的任何其他类型的测试或代码一样对待快照。

通过保持快照的重点、简短并使用强制执行这些风格约定的工具来确保您的快照可读。

如前所述,Jest 用于pretty-format使快照具有人类可读性,但你可能会发现引入其他工具很有用,例如eslint-plugin-jest使用其no-large-snapshots选项或snapshot-diff组件快照比较功能,以促进提交简短、重点突出的断言。

目标是让审查拉取请求中的快照变得容易,并与在测试套件失败时重新生成快照的习惯作斗争,而不是检查其失败的根本原因。

2. 测试应该是确定性的

你的测试应该是确定性的。在未更改的组件上多次运行相同的测试应该每次都产生相同的结果。你有责任确保生成的快照不包含特定于平台的数据或其他非确定性数据。

例如,如果你有一个使用的Clock组件​Date.now()​,则每次运行测试用例时,从该组件生成的快照都会不同。在这种情况下,我们可以模拟 Date.now() 方法以在每次测试运行时返回一致的值:

  1. Date.now = jest.fn(() => 1482363367071);

现在,每次快照测试用例运行时,​Date.now()​都会​1482363367071​一致地返回。无论何时运行测试,这都会导致为此组件生成相同的快照。

3. 使用描述性快照名称

始终努力为快照使用描述性测试和/或快照名称。最好的名称描述了预期的快照内容。这使得审阅者在审阅期间更容易验证快照,并且任何人都可以在更新之前知道过时的快照是否是正确的行为。

例如,比较:

  1. exports[`<UserName /> should handle some test case`] = `null`;
  2. exports[`<UserName /> should handle some other test case`] = `
  3. <div>
  4. Alan Turing
  5. </div>
  6. `;

到:

  1. exports[`<UserName /> should render null`] = `null`;
  2. exports[`<UserName /> should render Alan Turing`] = `
  3. <div>
  4. Alan Turing
  5. </div>
  6. `;

由于后者准确地描述了输出中的预期内容,因此更清楚地看到什么时候出错:

  1. exports[`<UserName /> should render null`] = `
  2. <div>
  3. Alan Turing
  4. </div>
  5. `;
  6. exports[`<UserName /> should render Alan Turing`] = `null`;

经常问的问题

快照是否在持续集成 (CI) 系统上自动写入?

不,从 Jest 20 开始,当 Jest 在 CI 系统中运行时没有明确传递​--updateSnapshot​. 预计所有快照都是在 CI 上运行的代码的一部分,并且由于新快照会自动通过,因此它们不应通过在 CI 系统上运行的测试。建议始终提交所有快照并将它们保留在版本控制中。

应该提交快照文件吗?

是的,所有快照文件都应该与它们所涵盖的模块及其测试一起提交。它们应该被视为测试的一部分,类似于 Jest 中任何其他断言的值。事实上,快照代表了源模块在任何给定时间点的状态。这样,当修改源模块时,Jest 可以分辨出与之前的版本相比发生了什么变化。它还可以在代码审查期间提供许多额外的上下文,审查人员可以在其中更好地研究你的更改。

快照测试仅适用于 React 组件吗?

ReactReact Native组件是快照测试的一个很好的用例。但是,快照可以捕获任何可序列化的值,并且应该在目标是测试输出是否正确的任何时候使用。Jest 存储库包含许多测试 Jest 本身输出的示例、Jest 断言库的输出以及来自 Jest 代码库各个部分的日志消息。请参阅Jest 存储库中对CLI 输出进行快照的示例。

快照测试和视觉回归测试有什么区别?

快照测试和视觉回归测试是测试 UI 的两种不同方式,它们用于不同的目的。视觉回归测试工具获取网页的屏幕截图并逐个像素地比较生成的图像。使用 Snapshot 测试值被序列化,存储在文本文件中,并使用 diff 算法进行比较。需要考虑不同的权衡,我们在Jest 博客中列出了构建快照测试的原因。

快照测试会取代单元测试吗?

快照测试只是 Jest 附带的 20 多个断言之一。快照测试的目的不是取代现有的单元测试,而是提供附加价值并使测试无痛。在某些情况下,快照测试可以潜在地消除对特定功能集(例如 React 组件)进行单元测试的需要,但它们也可以一起工作。

快照测试在生成文件的速度和大小方面的性能如何?

Jest 在重写时考虑了性能,快照测试也不例外。由于快照存储在文本文件中,因此这种测试方式既快速又可靠。Jest 为调用​toMatchSnapshot​匹配器的每个测试文件生成一个新文件。快照的大小非常小:作为参考,Jest 代码库本身中所有快照文件的大小都小于 300 KB。

如何解决快照文件中的冲突?

快照文件必须始终代表它们所覆盖的模块的当前状态。因此,如果您正在合并两个分支并在快照文件中遇到冲突,您可以手动解决冲突或通过运行 Jest 并检查结果来更新快照文件。

是否可以将测试驱动的开发原则应用于快照测试?

虽然可以手动写入快照文件,但这通常是不可行的。快照有助于确定测试覆盖的模块的输出是否已更改,而不是首先提供设计代码的指导。

代码覆盖率是否适用于快照测试?

是的,以及任何其他测试。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号