JS 自动化测试实践:`Jest` / `Vitest` 单元测试与快照测试

(清清嗓子,拿起麦克风)

嘿,大家好!欢迎来到今天的“JS 自动化测试实践”小课堂。我是今天的讲师,大家可以叫我老码。今天咱们不搞那些虚头巴脑的概念,直接上手,把 JestVitest 这两个测试界的当红炸子鸡玩个明明白白,顺便聊聊单元测试和快照测试那些事儿。

开场白:为什么要搞自动化测试?

在开始之前,先问大家一个问题:谁没被 Bug 搞崩溃过?(台下一片举手声)

OK,看来大家都深有体会。手动测试效率低、容易出错、还特别枯燥,简直是程序员的噩梦。自动化测试就像一个不知疲倦的机器人,帮你一遍又一遍地检查代码,提前发现 Bug,让你有更多的时间摸鱼…啊不,是优化代码,提升用户体验。

第一部分:Jest 初体验:你的第一个单元测试

Jest 是 Facebook 出品的,特点是配置简单、功能强大,号称“零配置”就能上手。虽然“零配置”有点夸张,但确实比很多测试框架要方便得多。

1. 安装 Jest

首先,我们需要安装 Jest

npm install --save-dev jest
# 或者
yarn add --dev jest

2. 编写被测函数

假设我们有一个简单的函数,用来计算两个数的和:

// src/sum.js
function sum(a, b) {
  return a + b;
}

module.exports = sum;

3. 编写测试用例

接下来,我们编写测试用例来验证这个函数是否正确:

// test/sum.test.js
const sum = require('../src/sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

test('adds -1 + 2 to equal 1', () => {
  expect(sum(-1, 2)).toBe(1);
});
  • describe: 用于组织测试用例,可以将相关的测试用例放在同一个 describe 块中,方便管理和阅读。
  • test: 一个测试用例,它接受一个字符串描述和一个函数,函数中包含断言。
  • expect: 用于创建断言,它接受一个值作为参数,并返回一个对象,该对象包含各种匹配器。
  • toBe: 一个匹配器,用于比较两个值是否相等。

4. 配置 package.json

package.json 文件中添加一个 test 脚本:

{
  "scripts": {
    "test": "jest"
  }
}

5. 运行测试

现在,我们可以运行测试了:

npm test
# 或者
yarn test

如果一切顺利,你应该会看到类似这样的输出:

 PASS  test/sum.test.js
  ✓ adds 1 + 2 to equal 3 (5ms)
  ✓ adds -1 + 2 to equal 1 (1ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.714s

恭喜你!你已经成功运行了你的第一个 Jest 单元测试。

第二部分:Jest 进阶:更多匹配器和异步测试

Jest 提供了丰富的匹配器,可以满足各种测试需求。

常用匹配器:

匹配器 描述 示例
toBe 比较两个值是否相等(使用 === expect(1 + 1).toBe(2);
toEqual 比较两个对象或数组是否相等(递归比较) expect({ a: 1 }).toEqual({ a: 1 });
toBeNull 检查值是否为 null expect(null).toBeNull();
toBeUndefined 检查值是否为 undefined expect(undefined).toBeUndefined();
toBeDefined 检查值是否已定义 expect(1).toBeDefined();
toBeTruthy 检查值是否为真值(truthy) expect(true).toBeTruthy();
toBeFalsy 检查值是否为假值(falsy) expect(false).toBeFalsy();
toBeGreaterThan 检查值是否大于另一个值 expect(2).toBeGreaterThan(1);
toBeLessThan 检查值是否小于另一个值 expect(1).toBeLessThan(2);
toBeCloseTo 比较浮点数是否接近另一个值,避免精度问题 expect(0.1 + 0.2).toBeCloseTo(0.3);
toContain 检查数组或字符串是否包含某个元素或子字符串 expect([1, 2, 3]).toContain(2);
toMatch 检查字符串是否匹配正则表达式 expect('hello').toMatch(/llo/);
toThrow 检查函数是否抛出异常 expect(() => { throw new Error(); }).toThrow();
not 用于取反匹配器 expect(1 + 1).not.toBe(3);

异步测试:

很多时候,我们需要测试异步函数,比如 Promise 或者 async/awaitJest 提供了几种方式来处理异步测试。

1. 使用 Promise:

// src/fetchData.js
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('peanut butter');
    }, 100);
  });
}

module.exports = fetchData;
// test/fetchData.test.js
const fetchData = require('../src/fetchData');

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

注意:需要在测试用例中返回 PromiseJest 会等待 Promise resolve 或 reject。

2. 使用 async/await:

// test/fetchData.test.js
const fetchData = require('../src/fetchData');

test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

使用 async/await 可以使异步测试代码更简洁易读。

3. 使用 resolvesrejects:

// test/fetchData.test.js
const fetchData = require('../src/fetchData');

test('the data is peanut butter', () => {
  return expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', () => {
  return expect(fetchData()).rejects.toThrow('error'); // 假设 fetchData 在出错时抛出 'error'
});

resolvesrejects 可以更方便地测试 Promise 的 resolve 和 reject 情况。

第三部分:Vitest 闪亮登场:更快更轻量级的选择

Vitest 是一个由 Vite 驱动的单元测试框架。如果你已经在使用 Vite 构建项目,那么 Vitest 可以无缝集成,带来更快的测试速度和更好的开发体验。

1. 安装 Vitest

npm install --save-dev vitest
# 或者
yarn add --dev vitest

2. 配置 Vitest

vite.config.js 文件中添加 test 配置:

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  test: {
    // 在这里配置 Vitest
  },
});

3. 编写测试用例

Vitest 的 API 和 Jest 非常相似,所以你可以直接使用 Jest 的语法来编写测试用例:

// test/sum.test.js
import { describe, expect, it } from 'vitest';
import sum from '../src/sum';

describe('sum', () => {
  it('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });

  it('adds -1 + 2 to equal 1', () => {
    expect(sum(-1, 2)).toBe(1);
  });
});

注意:Vitest 使用 import 语句来导入模块,而不是 require

4. 运行测试

package.json 文件中添加一个 test 脚本:

{
  "scripts": {
    "test": "vitest"
  }
}

然后运行测试:

npm test
# 或者
yarn test

Vitest 最大的优势在于它的速度。由于它使用了 Vite 的底层技术,因此测试速度非常快,尤其是在大型项目中,可以显著提升开发效率。

第四部分:快照测试:你的 UI 卫士

快照测试是一种非常有用的测试技术,它可以帮助你检测 UI 组件是否发生了意外的变化。

1. 什么是快照?

快照就是一个 UI 组件在特定状态下的渲染结果的文本表示。当你第一次运行快照测试时,JestVitest 会生成一个快照文件,并将 UI 组件的渲染结果保存到文件中。

2. 快照测试的原理

在后续的测试中,JestVitest 会将 UI 组件的当前渲染结果与快照文件进行比较。如果两者不一致,测试就会失败,提示你 UI 组件发生了变化。

3. 快照测试的用途

  • 检测 UI 组件是否发生了意外的变化。
  • 验证 UI 组件的渲染结果是否符合预期。
  • 跟踪 UI 组件的变化历史。

4. Jest 中的快照测试

// src/Button.js
import React from 'react';

function Button({ children }) {
  return <button>{children}</button>;
}

export default Button;
// test/Button.test.js
import React from 'react';
import { render } from '@testing-library/react';
import Button from '../src/Button';

test('renders a button with text', () => {
  const { asFragment } = render(<Button>Click me</Button>);
  expect(asFragment()).toMatchSnapshot();
});
  • @testing-library/react:一个用于测试 React 组件的库。
  • render:用于渲染 React 组件。
  • asFragment:返回一个包含渲染结果的 Fragment。
  • toMatchSnapshot:将渲染结果与快照文件进行比较。

第一次运行测试时,Jest 会生成一个快照文件:test/__snapshots__/Button.test.js.snap,内容如下:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders a button with text 1`] = `
<DocumentFragment>
  <button>
    Click me
  </button>
</DocumentFragment>
`;

如果修改了 Button 组件的渲染逻辑,再次运行测试时,测试就会失败,提示你快照文件与当前渲染结果不一致。

5. Vitest 中的快照测试

Vitest 的快照测试 API 与 Jest 非常相似:

// test/Button.test.js
import React from 'react';
import { render } from '@testing-library/react';
import Button from '../src/Button';
import { expect, it } from 'vitest'; // 引入 Vitest 的 expect 和 it

it('renders a button with text', () => {
  const { asFragment } = render(<Button>Click me</Button>);
  expect(asFragment()).toMatchSnapshot();
});

6. 快照测试的最佳实践

  • 只对 UI 组件进行快照测试,不要对业务逻辑进行快照测试。
  • 定期更新快照文件,以反映 UI 组件的变化。
  • 在代码审查中仔细检查快照文件的变化。

第五部分:Jest 和 Vitest 的选择:鱼与熊掌?

特性 Jest Vitest
易用性 配置简单,上手容易 配置相对简单,但需要了解 Vite 的配置
速度 较慢 非常快,尤其是在大型项目中
集成度 与各种工具和框架集成良好 与 Vite 集成无缝,对其他框架支持较好,但可能需要额外配置
功能 功能强大,提供了丰富的匹配器和 API 功能完善,API 与 Jest 相似,学习成本低
社区支持 庞大的社区,丰富的资源和插件 社区活跃,但相对较小

如何选择?

  • 如果你已经在使用 Vite 构建项目,并且追求更快的测试速度,那么 Vitest 是一个不错的选择。
  • 如果你需要更成熟的测试框架,并且需要与各种工具和框架集成,那么 Jest 仍然是首选。

当然,你也可以同时使用 JestVitest。例如,你可以使用 Jest 进行集成测试,使用 Vitest 进行单元测试。

总结:

自动化测试是保证代码质量的关键环节。JestVitest 都是优秀的 JavaScript 测试框架,它们可以帮助你编写可靠的测试用例,提前发现 Bug,提升开发效率。希望今天的课程能够帮助大家更好地理解和应用自动化测试。

(放下麦克风,鞠躬)

谢谢大家!大家可以提问了!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注