不同的测试类型与方案

使用 Vitest

Vitest 是一款高性能、与 Vite 深度集成的测试框架,适合现代前端开发中的多种测试需求:

1. 常见的单元测试需求

  • 适用场景:

    • 验证函数、模块或组件的正确性。
    • 对独立的逻辑单元进行验证,例如工具函数、数据处理逻辑等。
  • 示例:

    import { describe, it, expect } from 'vitest';
    
    const add = (a: number, b: number) => a + b;
    
    describe('add function', () => {
    it('adds two numbers correctly', () => {
      expect(add(1, 2)).toBe(3);
    });
    });

2. 基准测试(Benchmark,侧重比较性能结果)

  • 适用场景:

    • 比较不同实现的性能差异。
    • 用于性能瓶颈分析。
  • 示例:

    import { bench } from 'vitest';
    
    bench('Array#map', () => {
    [1, 2, 3].map(x => x * 2);
    });
    
    bench('For loop', () => {
    const result = [];
    for (let i = 0; i < 3; i++) {
      result.push(i * 2);
    }
    });

3. 覆盖率测试

  • 适用场景:

    • 确保代码的每个部分都被测试到。
    • 生成覆盖率报告,用于评估测试质量。
  • 示例:

    vitest.config.ts

    中启用覆盖率:

    export default defineConfig({
      test: {
        coverage: {
          reporter: ['text', 'html'], // 覆盖率报告输出格式
        },
      },
    });

4. 类型测试

  • 适用场景:

    • 测试 TypeScript 类型定义的行为。
    • 验证类型推断是否符合预期。
  • 示例:

    import { expectTypeOf } from 'vitest';
    
    const add = (a: number, b: number) => a + b;
    
    expectTypeOf(add).toBeFunction();
    expectTypeOf(add).parameters.toEqualTypeOf<[number, number]>();

5. 源码内联测试

  • 适用场景:

    • 在源码文件中直接编写与其相关的测试。
    • 适合验证小型模块的行为。
  • 示例: 在源码文件中:

    export function multiply(a: number, b: number): number {
    return a * b;
    }
    
    // @vitest-environment happy-dom
    if (import.meta.vitest) {
    it('multiplies two numbers', () => {
      expect(multiply(2, 3)).toBe(6);
    });
    }

6. 测试快照

  • 适用场景:

    • 测试组件或对象的结构在多次运行时保持一致。
    • 检测 UI 输出是否发生意外变化。
  • 示例:

    import { describe, it, expect } from 'vitest';
    
    const generateObject = () => ({ foo: 'bar', baz: 'qux' });
    
    describe('Snapshot Test', () => {
    it('matches the snapshot', () => {
      expect(generateObject()).toMatchSnapshot();
    });
    });

使用 test-storybook

test-storybook 是专门为组件开发设计的测试工具,基于 Storybook 环境,结合 UI 的 DOM 结构测试。

适用场景

  1. 组件交互测试
    • 验证组件在不同交互状态下的表现。
  2. 基于 DOM 的 UI 测试
    • 直接测试组件渲染输出是否符合预期。
  3. 与 Storybook 整合
    • 重用 Storybook 的故事作为测试用例。

使用其他自动化截图对比测试工具和方案

对于复杂的 UI 组件(如基于 Canvas 的渲染组件),可以通过截图对比工具检测输出是否发生变化。

适用场景

  • 测试基于 CanvasSVG 的组件渲染结果。
  • 用于检测视觉回归问题。

常见工具

  1. Percy
    • 自动化截图对比工具,适合与 CI/CD 集成。
  2. Puppeteer + Pixelmatch
    • 自定义截图测试方案,基于 Headless 浏览器和像素比对。

方案示例

  • 基于 Puppeteer 和 Pixelmatch:

    const puppeteer = require('puppeteer');
    const pixelmatch = require('pixelmatch');
    const { PNG } = require('pngjs');
    const fs = require('fs');
    
    (async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('http://localhost:3000');
    
    const screenshot = await page.screenshot();
    const baseline = PNG.sync.read(fs.readFileSync('./baseline.png'));
    const comparison = PNG.sync.read(screenshot);
    
    const diff = new PNG({ width: baseline.width, height: baseline.height });
    const mismatchedPixels = pixelmatch(
      baseline.data,
      comparison.data,
      diff.data,
      baseline.width,
      baseline.height,
      { threshold: 0.1 }
    );
    
    fs.writeFileSync('./diff.png', PNG.sync.write(diff));
    console.log(Mismatched Pixels: ${mismatchedPixels});
    
    await browser.close();
    })();

对比总结

测试类型 工具 优势 适用场景
单元测试 Vitest 快速、简单、支持 TS 和内联测试 函数、逻辑模块、简单组件
性能测试 Vitest (Benchmark) 可对比不同算法或实现的性能 性能优化和瓶颈分析
覆盖率测试 Vitest 自动生成覆盖率报告 评估测试覆盖范围
类型测试 Vitest 确保类型定义的正确性和推断结果 TypeScript 项目
测试快照 Vitest 快速验证结构或输出的一致性 UI 渲染或结构化数据测试
DOM 组件测试 test-storybook 整合 Storybook,专注 UI 和交互状态 组件交互测试
截图对比测试(视觉回归测试) Puppeteer + Pixelmatch 可用于复杂 Canvas 或 SVG 的渲染测试 基于视觉的回归问题检测

通过这些工具和方案,你可以针对不同测试类型选择合适的工具,高效完成各类测试需求。

Vue3 项目快速上手 Vitest

将 Vitest 安装到项目

Vitest 需要 Vite >= v5.0.0 且 Node >= v18.0.0

npm:

npm install -D vitest

yarn:

yarn add -D vitest

pnpm:

pnpm add -D vitest

进行推荐的基础配置

vite.config.ts的顶部添加/// <reference types="vitest/config" />三斜杠指令以提供TS类型提示。

然后在vite.config.tsdefineConfig方法的入参对象中添加test属性,如下所示为个人推荐的配置:

/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
// import ...

export default defineConfig({
  // ...
  test: {
    // Vitest 配置
    coverage: {
      // 覆盖率测试 相关配置
      provider: 'v8', // 覆盖率测试的引擎
      reporter: ['text', 'json', 'html'], // 测试报告的输出文件类型
      include: ['src/utils', 'src/components'], // 覆盖率测试的范围
    },
    includeSource: ['src/utils/**/*.{js,ts}', 'src/components/**/*.{js,ts}'], // 源码内联测试的范围
  },
})

还可以在package.json中配置常用的测试脚本:

{
  "scripts": {
    "test": "vitest",
    "coverage": "vitest run --coverage",
  }
}

常用的终端命令

  • vitest:在当前目录中启动Vitest。在开发环境会自动进入监听(watch)模式,在CI环境会自动进入运行(run)模式。

    默认的包含与排除规则:

    include: **/*.{test,spec}.?(c|m)[jt]s?(x)
    exclude:  **/node_modules/**, **/dist/**, **/cypress/**, **/.{idea,git,cache,output,temp}/**, **/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*

    可以添加--ui选项来启用 Vitest UI ,这个界面是可选的,可以通过以下命令安装:

    npm i -D @vitest/ui
  • vitest run:在没有监听模式的情况下执行单次运行。

  • vitest bench:仅运行基准测试,比较性能结果。

  • vitest list:该命令继承所有的vitest选项以打印所有匹配测试的列表。

    可以传递--json标志以 JSON 格式打印测试,也可以将其保存在单独的文件中:

    vitest list filename.spec.ts -t="some-test" --json=./file.json
    describe > some-test
    describe > some-test > test 1
    describe > some-test > test 2

    如果--json标志没有接收到值,它将把 JSON 输出到 stdout 中。

    还可以传递--filesOnly标志来仅打印测试文件:

    vitest list --filesOnly
    tests/test1.test.ts
    tests/test2.test.ts

Globals 配置与自动导入

Vitest通过其vi辅助工具提供实用功能来帮助我们进行测试。默认情况下,vitest 不显式提供全局 API。而需要从vitest中导入:

import { vi } from 'vitest';

也可以启用Globals 配置来全局访问它:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
  },
})

为了可以让全局 API 支持 TypeScript,请将 vitest/globals 添加到 tsconfig.json 中的 types 选项中:

{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

如果已经在项目中使用 unplugin-auto-import,也可以直接用它来自动导入这些 API:

import { defineConfig } from 'vitest/config'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      imports: ['vitest'], // 添加 vitest
      dts: true, // generate TypeScript declaration
    }),
  ],
})

基于 Vitest 的常用测试

单元测试

在相应目录下创建xxx.test.js/ts文件来编写相关的测试用例。

简单示例请参考Vitest - 快速起步 - 编写测试

了解更多关于 Vitest 的使用,请参考 API 索引 部分。

覆盖率测试

基础配置已在上文Vue3 项目快速上手 Vitest => 进行推荐的基础配置说明,其他详见官网文档Vitest - 测试覆盖率

测试快照

官网文档写的非常简洁明了,建议直接看官方文档[Vitest - 测试快照]。

类型测试

详见官网文档[Vitest - 类型测试]。

源码内联测试

Vitest 还提供了一种方式,可以运行与你的代码实现放在一起的测试,就像是 Rust 语言的模块测试一样。

这允许测试与实现共享相同的闭包,并且能够在不导出的情况下针对私有状态进行测试。同时,它也使开发更加接近反馈循环。

官网文档写的非常简洁明了,建议直接看官方文档[Vitest - 源码内联测试]。

相关资料


A Student on the way to full stack of Web3.