[Day 26] 快照测试(Snapshot Testing)是什麽?什麽时间适合使用?

Snapshot Testing

快照测试第一时间听起来好像是会「帮我们的画面做一个快照,纪录下来当时的画面」,但这样的说法对也不对,今天就让我们来了解一下所谓的快照测试(Snapshot Testing)。

快照测试的原理

快照测试(Snapshot Testing)是由 Jest 提供的一种测试方式,它的原理是在进行测试时,Jest 会把开发者指定的元素转译(render)成 DOM 後保存成一个档案纪录下来,一般这个档案的副档名会以 .snap 结尾。

快照测试的原理蛮单纯的,它就是在执行测试时,比对当前的快照档内容和前一次的快照档是否相同,如果是第一次执行还没有前一次的快照可以比较的话,就会自动生成一份新的快照档。如果当前快照档和前一次的快照档内容不同的话,测试就不会通过,也就是说,只要这次转译出来的 DOM 和前一次纪录下来的 DOM 有任何不同,测试都不会通过。

举例来说,如果现在有一个 React 元件 Button

// Button.tsx
const Button = () => {
  return <button>Click me</button>;
};

export default Button;

在第一次执行快照测试时,Jest 就会自动建立一个 __snapshots__ 资料夹,且里面有一只名为 Button.test.tsx.snap 的档案,而它的快照档内容则会长像这样:

// __snapshots_/Button.test.tsx.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`create snapshot test for the Button 1`] = `
<button>
  Click me
</button>
`;

现在如果把原本 Button 元件中的文字从 Click me 改成 Click here 的话,测试就会失败:

Snapshot Testing

也就是说,只要「这次 render 出来的 DOM 和前一次纪录的 DOM 不同」测试就不会通过。

为什麽需要快照测试(Snapshot Testing)

从快照测试的原理可以知道,只要画面和过去有任何不同,测试就会报错,这样听起来,几乎只要每次程序有改动,测试结果就一定会失败(failed),这麽说起来快照测试到底有什麽好处呢?

快照测试最重要的目的是提醒开发者:「这里有不同喔!这个不同是你预期该有的不同吗?

你可以想像有些时候,特别是一些单纯的 UI 元件,花许多时间去写这个元件的 Unit Test 可能 CP 值不太高,因为在写测试时你需要先把该元件 render 出来,接着透过一些 selector 去选到该元素,然後透过 assert 确保该元素真的有 render 出来,并且在 assert 元素里的内容是相同的。举例来说,刚刚那个简单的 Button 元素,写成 Unit Test 会像这样:

test('button can render successfully with correct text', () => {
  render(<Button />);
  const linkElement = screen.getByText(/Click me/i);
  expect(linkElement).toBeInTheDocument();
});

但一般的 UI 都不会这麽简单,如果要一个个选出来再 assert 的话,相当麻烦。

这时有快照测试的话就相当方便,只需在开发好元件,且确认 UI 是正确的後,为它拍一个快照纪录下来,後续只要这个 UI 的内容有变更,测试在执行的时候都会 failed,以提示开发者:「这里有不同喔!请留意!」。

更新快照

当开发者发现测试错误时,就要去检视这个差异是预期的吗?以刚刚来说,因为我们把原本 Button 元件的文字内容从 Click me 改成 Click here,所以现在这个错误是合理且符合预期的:

Snapshot Testing

这时候开发者要做的就是更新快照,在 Jest 的 watch mode 中,可以看到更新快照的选项:

Snapshot Testing

可以透过 u(更新所有有问题的快照) 或 i(逐一选择要更新的快照)来更新快照,更新完後,测试就会通过,同时原本的快照档也会更新成当前 DOM 的样子。

原则上这些快照档也都需要一起透过 git 来追踪,因此在发 PR 的时候,reviewer 也可以很容易的掌握 UI 上有变更的部分:

Snapshot Testing

撰写快照测试

在了解了快照测试的概念後,来看一下刚刚 Button 元件的快照档是如何产生(这里以 React 元件为例):

// Button.test.tsx
import Button from './Button';
import { create } from 'react-test-renderer';

test('<Button /> should render click here', () => {
  const tree = create(<Button />).toJSON();
  expect(tree).toMatchSnapshot();
});

就是这麽容易,首先载入需要被快照的元件,并使用 React 官方提供的 react-test-renderer 套件(需要自行 npm install)。接着透过 createtoJSON() 将 Button 元件变成一个可以被保存下来的物件,最後呼叫 toMatchSnapshot() 这个方法,Jest 就会帮我们建立以及比对 .snap 档。

快照测试的使用时机及注意事项

从上面的说明中可以知道,快照测试这种一旦画面有变更就要提示的特性,比较适合用在 UI 元件/函式库,因为这些 UI 元件通常写好後被变更的机会较小,且 UI 本身的正确性是它的重点;快照测试无法检测元件的逻辑和模拟使用者的互动,例如点了按钮下拉选单要展开这类的,因此如果有这个需求,需要辅以其他单元测试或整个测试,但至少快照测试可以确保 UI 的稳定性。

注:快照测试虽然无法直接测试使用者互动的情况,但可以透过 props 的方式来执行某些 event listeners 来促使画面改变,详细的方式可以参考 snapshot-testing @ Jest

看起来快照测试着重的是 UI,那为什麽今天的开头提到「快照测试是帮我们的画面做一个快照,纪录下来当时的画面」这种说法对也不对呢?

原因在於快照测试纪录下来的是 DOM 本身,而不是使用者真正看到的画面,快照测试不像 visual regression testing 这种会实际以画面截图进行像素比对所进行的测试,因此精确来说,快照测试纪录的只是 DOM,而不是真实的画面

最後,快照测试所产生的 snap 档记得要被 git 所追踪和纪录,如此才能正确测试 DOM 有无变更,也能更清楚每次 DOM 的变化,因此记得不要把它们给 ignore 了。

参考资料


<<:  [Day_27]函式与递回_(6)

>>:  【程序】交朋友 转生成恶役菜鸟工程师避免 Bad End 的 30 件事 - 28

追求JS小姊姊系列 Day11 -- 流程错了怎办?难道要跟D特终老?

前情提要 终於做出时间了,却又卡在流程问题,这次解决是否能顺利回到过去? 我:那要怎麽样避免因为流程...

Day28_CSS语法11

border-radius(框线圆角) border-top-left-radius : 左上角显示...

[Tableau Public] day 7:尝试制作不同种类的报表-4

第七天,想不到我坚持了一星期~继续加油! 参考的资料来源一样是 day 4 的「Our World ...

[访谈] APCS x 自学生 Jason

今天邀请到和我相同年纪,但目前已经在业界工作的 Jason 分享,在整个访谈过程刷新了身为小小学生的...

[ Day 27 ] - 样板字面值(Template literals)

说明 在先前的版本中被称为样板字串(template strings) 早期在组字串资料时会用大量的...