快照测试的优点和缺点介绍

1,235 阅读8分钟

在过去的几年里,快照测试在前端开发中变得非常流行。这个词几乎成了Jest和React的同义词,但它可以用来测试更多的组件。本文简要介绍了什么是快照测试,什么不是,以及它如何对你的项目有帮助。

什么是快照测试?

快照测试是一种 "输出比较 "或 "金主 "测试。这些测试通过比较应用程序或组件的当前特性与这些特性的存储 "良好 "值来防止回归。快照测试从根本上不同于单元和功能测试。虽然这些类型的测试对应用程序的正确行为进行了断言,但快照测试只断言现在的输出与之前的输出是一样的;它没有说在任何情况下的行为是否正确。

考虑一下下面的两个登录表格:

原来的登录表格

当前的登录表格

左边的是原来的,右边的是最近风格改变后的表格。基于图像的快照测试会注意到这种变化并突出显示,就像下面Intern的visual-plugin的报告那样。

这个测试并没有说新旧风格是否正确,它只是向测试者强调了一些变化。

可以测量各种特性,但前端测试通常集中在两个方面:数据(可序列化的JavaScript值)和图像。例如,Depicted将渲染的页面或组件的图像与储存的渲染实体的图像进行比较,并标记出任何 "感知 "差异。批准测试将JavaScript值,如应用程序函数的输出,与存储的好值进行比较,类似于单元测试通常的工作方式。Jest的快照测试也适用于可序列化的JavaScript值,但它们最常被应用于React组件的基于DOM的渲染树。

快照测试并不是一个新概念。该术语传统上指的是视觉回归测试,即把渲染的应用程序或页面的字面快照与存储的图像进行比较。然而,Jest的渲染测试是真正将该术语带入现代前端开发人员的主流,这就是 "快照测试 "在JavaScript世界的典型含义。

快照测试如何工作

快照测试的工作原理是记录系统的某些特性(例如,拍摄快照),然后将存储的快照与该特性的当前值进行比较。对于Jest风格的测试,该特性通常是一个序列化的渲染树:


const elem = renderer.create(<MyComponent label="foo">).toJSON();
expect(elem).toMatchSnapshot();

第一次运行测试时,toMatchSnapshot期望将它收到的数据保存到一个文件中。在这种情况下,这就是elem,一个序列化的渲染树,它可能看起来像:


<my-component
  label="foo"
  className="component"
/>

在以后的测试运行中,elem的当前值会与存储的 "好 "值进行比较。如果这两个值不一样,测试就会失败。

其他以数据为重点的快照测试工具也有类似的工作。使用snap-shot-itMocha插件实现的同样的测试看起来像:


const elem = renderer.create(<MyComponent label="foo">).toJSON();
snapshot(elem);

快照最终会与它们所代表的组件不同步,必须得到更新。工具一般都会让这个问题变得简单。例如,Jest的快照可以通过运行以下程序来更新:


jest --updateSnapshot

更新快照非常容易,这使得它们非常容易维护,但这既是一种祝福,也是一种诅咒(后面会详细介绍)。

快照测试的好处

编写测试可能是一个巨大的时间沉淀。当Jest的快照测试功能首次公布时,开发人员说:"......工程师经常告诉我们,他们花在编写测试上的时间比组件本身还要多"。这导致许多开发人员说他们干脆完全不写测试了。

在这种情况下,快照测试可以提供很大的帮助,因为它们通常比传统的单元测试更短,更容易编写,这个快照测试:


const elem = renderer.create(<MyComponent label="foo">).toJSON();
expect(elem).toMatchSnapshot();

比这个单元测试要简单得多:


const elem = renderer.create(<MyComponent label="foo">);
expect(elem).toHaveProperty('type', 'my-component');
expect(elem).toHaveProperty('props.label', 'foo');
expect(elem).toHaveProperty('props.className', 'base-component');

快照测试也很容易保持更新,因为开发人员通常只需要运行一个命令,让测试系统记录新的快照。这当然比需要编辑许多测试文件来使测试与现实同步要容易得多。

快照测试的工具

有相当多的工具可用于快照测试前端代码。

一些工具对可序列化的数据进行快照。如前所述,Jest 内置了对快照测试的支持,并经常被用来测试React组件。Cypress通过插件支持快照测试,比如官方的@cypress/snapshotApproval Tests支持一些语言的快照测试,包括JavaScript。snap-shot-itJavaScript库将快照测试功能添加到基于JavaScript的BDD测试框架中,如Mocha

许多前端工具专注于视觉快照而不是数据。Intern通过其visual-plugin支持简单的可视化回归测试。Jest-Image-Snapshot插件为Jest增加了可视化快照功能。Storybook,一个UI开发系统,本身并不支持快照测试,但它提供了一个渲染平台,许多其他工具都在使用Applitools执行 "视觉AI测试"。它比较图像的方式更像人类,忽略了不易察觉的差异,如不同浏览器、浏览器版本和不同操作系统上的浏览器之间的微小字体和图像渲染差异。

上面提到的大多数工具是用于本地测试的,但也有一些基于云的视觉快照测试服务。这些工具负责管理测试可能运行的各种浏览器,并存储测试快照。Chromatic是基于Storybook的。Percy可以与Storybook一起工作,也可以与一系列其他测试系统一起工作。

快照测试的弊端

虽然快照测试比传统的单元或功能测试更容易编写和保持更新,而且它们可以成为防止应用程序回归的有效工具,但它们确实有几个潜在的缺点。

一个重要的缺点是,它们与应用程序的输出紧密耦合,使它们非常脆弱。任何变化,即使是对输出的无关紧要的部分,都会导致快照测试失败。然后,开发人员必须(或至少应该)手动验证一切是否仍在正常工作并更新快照。

这导致了快照测试的另一个潜在问题:它们实际上并没有说明任何关于预期输出的问题,只是说明它不应该改变。与单元和功能测试不同,快照测试不包含重点的、有意义的断言或期望。开发人员必须手动验证输出是否仍然 "良好",当失败的测试是针对他或她不熟悉的应用程序的一部分时,可能会遇到麻烦,因为快照测试没有指出输出的哪些部分是重要的。

快照测试在本质上并不适合动态内容。一个 "每日随机报价 "的组件将经常无法通过快照测试,因为输出中的随机报价通常与存储的组件数据不符。工具可以通过让用户标记动态内容的区域来处理这个问题。

例如,Jest提供了 "非对称匹配器",可以在创建快照时用来识别动态元素。在下面的片段中,userData值中的一些属性可能在不同的测试会话中有所不同。`expect.any`指令告诉Jest接受指定类型的任何值,而不是最初创建快照时捕获的特定值。


expect(userData).toMatchSnapshot({
    createdAt: expect.any(Date),
    id: expect.any(Number),
  });

某些类型的动态内容可能更有问题。例如,JavaScript和CSS动画会随着时间的推移改变一个页面或组件的视觉表现。像Percy和Chromatic这样的工具有处理动态内容的技术,尽管仍然需要一些开发人员的干预。

快照测试的另一个潜在缺点是存储要求。快照必须存储在某个地方;通常它们被检查到项目库中。根据被记录的特征,快照可能相当大。渲染树是基于文本的,一般来说,扩散和压缩都很好,但大量基于屏幕截图的快照测试套件可以很容易地消耗几十兆的空间。这是另一个领域,基于云的服务可以通过存储视觉测试的快照提供一些帮助。

总结

快照测试很容易创建和维护,它们是检查你的应用程序的行为在开发过程中没有意外变化的好方法。然而,它们不能替代单元和功能测试,因为单元和功能测试可以验证应用程序是否正常工作,而不仅仅是验证它是否有变化。