编写快照测试

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26

了解快照测试

一个对快照测试简单的解释就是获取代码的快照,并将其与以前保存的快照进行对比。如果新的快照与前一个快照不匹配,测试就会失败。

Jest 快照测试会对比序列化值(serializable value),基本上任何可以转换为字符串的 JavaScript 代码都是序列化值。

expect('value').toMatchSnapshot();

expect(document.querySelector('div')).toMatchSnapshot();

快照测试第一次启动时,Jest 会用传递给 expect 的值来创建快照文件

test('reders list item correctly', () => {
  const wrapper = shollowMount(ListItem);
  // 假设 ListItem 就渲染了一个 <li> 标签
  expect(wrapper.element).toMatchSnapshot();
})

当你在 Jest 中运行快照测试时,Jest 会使用 expect 调用的 DOM 节点来生成格式化的文件然后存储,会在未来的测试中进行对比,该文件示例如下:

exports[`reders list item correctly`] = `
  <li>
    ListItem content.
  </li>
`

下次运行快照测试时,它会将 expect 调用的新值与快照文件中的保存的值进行比较,如果输出匹配,快照测试将通过。如果失败,快照测试将带着差异(diff) 失败,可帮助查看那个部分已被更改。

如果新值中的内容被更改为了 'Not List',那么就会有以下 diff。

- Snapshot
+ Received
  <li>
- ListItem content.
+ Not List
  </li>

大概的流程图如下:

快照测试流

Jest 可以为你管理快照文件。快照文件是以 .snap 为扩展名,生成在 __snapshots__ 中的文件,该目录与测试文件会在同一目录中被创建,可以删除和覆盖之前保存的快照。快照文件是快照测试输出的唯一真实源,因此应该在源代码管理中包含快照文件,以便在不同设备上运行测试时使用。

为静态组件编写快照测试

静态组件指的是总是渲染相同输出的组件,它不接受任何 prop,也没有任何 state。组件中没有任何逻辑,并且总是会渲染相同的 HTML 元素。

为静态组建编写单元测试是没有必要的,但是在最初编写完静态组件并手动测试它之后,想要确保静态组件在未来不会发生更改,单元测试就变得非常有用了。

<!-- 静态组件  Spinner.vue -->
<template>
  <transition>
    <svg class="spinner" width="44px" height="44px" viewBox="0 0 44 44">
      <circle 
        class="path" fill="none" stroke-width="4" stroke-linecap="round"
        cx="22" cy="22" r="20"
      >
      </circle>
    </svg>
  </transition>
</template>

快照测试代码如下:

import { shallowMount } from '@vue/test-utils';
import Spinner from '../Spinner.vue';

describe('', () => {
  test('', () => {
    expect(shallowMount(Spinner).element).toMatchSnapshot();
  })
})

运行测试套件后,检查控制台的输出内容,你会发现 Jest 创建了一个 snap 文件,如果测试文件是 src/__tests__/compoents/Spinner.spec.js,那么对应的 snap 文件就是 src/__tests__/compoents/__snapshots__/Spinner.spec.js.snap,查看该文件,你会发现 element 的值被写入到了这个文件之中。

如果在这之后修改一下 Spinner.vue 中的 svg 标签的属性,在运行测试,快照测试就会失败,因为它会与之前保存的进行比较。

你也可以使用 --update 标识调用 Jest,重写 snap,这样就不会与之前保存的进行比较,而是直接覆盖,即将本次的快照当做初始快照。

npm run test:unit -- --updateSnapshot

# 或
npm run test:unit -- --u

这行命令会告诉 Jest 去重写所有失败的快照文件,当你想重写多个快照文件时这个方式很实用,但是这个操作比较危险,它可能会意外地添加错误的快照,为了避免错误的快照被添加,你可以使用交互模式启动 Jest,使用一下命令运行交互式更新快照模式。

npm run test:unit -- --watch

当提示符在终端出现时,按 i 键浏览所有失败的快照测试,按 u 键可以使用新值来更新之前保存的快照文件。交互模式是一种同时验证多个快照的安全办法。

为动态组件编写快照测试

这里的动态组件指的是包含逻辑和状态的组件,比如说,点击按钮时它们会传递 prop 或更新数据。

当你为动态组件编写快照测试时,应该尝试捕获尽可能多的不同组合的状态,这样,快照测试将尽可能多地覆盖功能。

假设 Item 组件会携带一个 item prop,该组件会使用跟这个对象渲染 HTML 标签,对于快照测试而言,你需要用真实数据创建 item prop,这么做的目的是让测试更加值得信赖,因为它的输出更接近于生产环境中的输出内容。

快照测试的一个准则是快照测试必须是可确定的。换句话说,如果生成输出的代码没有改变,那么输出应该总是相同的,不管启动多少次测试都应该是这样,不过当你使用了会输出不确定结果的方式时,就产生了一个问题,比如 Date.now。在你第一次运行之后,渲染出来的是 3minutes ago,你第二天再次运行的时候,渲染出来的 1day ago了。想要避免这种问题,你就需要模拟 Date.now 方法,让它总是返回相同的时间。以此使你的快照测试具有确定性。

test('renders correctly', () => {
  const dateNow = jest.spyOn(Date, 'now');
  const dateNowTime = new Date('2018');

  dateNow.mockImplementation(() => dateNowTime);

  const item = {
    by: 'eddyerburgh',
    id: 11122233,
    score: 10,
    time: dateNowTime - (1000 * 600),
    title: 'vue-test-utils is released',
    type: 'story',
    url: 'https://vue-test-utils.vuejs.org/'
  }
  const wrapper = createWrapper({
    propsData: {
      item
    }
  })
  dateNow.mockRestore();
  expect(wrapper.element).toMatchSnapshot();
})

编写快照测试来捕获条件分支下的内容。

假设 Item 的部分实现为:

<span v-if="item.type !== 'job'" class="by">
  by <router-link :to="'/user/' + item.by">{{ item.by }}</router-link>
</span>
<span v-if="item.type !== 'job'" class="comments-link">
  | <router-link :to="'/item/' + item.id">{{ item.descendants }} comments</router-link>
</span>
<span>
  {{ item.time | timeAgo }} ago
</span>

编写多种情况下的快照测试,上面的快照包含的 type 为 story,这个快照把 type 设置为 job,使用不同的分支内的内容进行渲染,生成多种情况下的快照。

test('renders correctly when item has no url', () => {
  // ... 省略部分与上面代码相同
  const item = {
    by: 'eddyerburgh',
    id: 11122233,
    score: 10,
    time: dateNowTime - (1000 * 600),
    title: 'vue-test-utils is released',
    type: 'job'
  }

  // ... 省略部分与上面代码相同
  expect(wrapper.element).toMatchSnapshot()
})

理想情况下,组件输出的所有分支都应该被快照测试覆盖到,但这并不总是可能的,也并不可取。一个组件的大量快照测试意味着每次更改该组件时,都会有大量失败的快照测试。如果有太多失败的快照测试,那么更新所有失败的测试可能会变得非常困难,并且可能会意外的保存一个错误的快照,一般来说,不建议对一个组件编写超过三个快照测试。

总结

单元测试对于测试组件中的逻辑是大有帮助的,然而对测试静态组件就显得力不从心,幸运的是,快照测试非常适用于测试静态组件的输出。

这时候才反过头去看按照 TDD 编写 Vue 组件的顺序 [跳转],你就知道在什么开始编写你的快照测试了。