Skyroc Admin Docs
工程化

测试

测试理念、分层策略与本项目 Vitest 测试方案

为什么测试

代码测试从来不只是「验证代码没有 bug」。它的本质是:为代码的行为立契约

一个有测试的函数,不仅告诉你它能跑通,更告诉你:

  • 它在哪些输入下做什么
  • 它的边界条件在哪里
  • 当有人改动它时,哪些行为不能被破坏

这份契约在 AI 时代变得比以往任何时候都重要。


AI 时代,测试的角色变了

AI 很擅长写「看起来对的代码」。它能快速生成符合语法、结构合理、逻辑流畅的实现。但 AI 不了解你的业务约束,不知道某个边界值背后藏着什么历史决策,也不会在意某个副作用是否符合你的领域规则。

没有测试的项目里,AI 放大的是风险。

你需要用极高的心智负担去 review 每一行生成的代码,逐字核实它的正确性。这和「让 AI 帮你干活」的初衷背道而驰。

有测试的项目里,AI 放大的是效率。

测试是客观的裁判。AI 写完代码,跑一遍测试,结果一目了然——通过意味着行为契约被满足,失败意味着 AI 踩到了约束边界。你的 review 可以真正聚焦在「方向对不对」「架构合不合理」,而不是「这段逻辑有没有漏洞」。


本项目推荐的 AI 协作工作流

整理 / 澄清需求

与 AI 探讨实现方案(架构、边界、数据流)

让 AI 起草 / 修改技术文档

加载规范 skill,让 AI 写代码

跑测试 ← 测试是裁判,不是人

git review(聚焦意图与架构,不再逐行验证正确性)

有明显问题 → 指出,让 AI 修改 → 循环

测试在这个流程里扮演的角色是自动化验收。它不是开发后的附加步骤,而是整个协作循环得以快速运转的前提。


测试分层策略

前端测试通常分三层,职责不同,成本各异:

层级工具覆盖什么不覆盖什么运行时机
单元测试Vitest纯函数、hooks、算法、工具库UI 渲染、用户流程每次提交
集成测试Vitest + MSW模块间协作、API 边界、状态流转浏览器行为、E2E 路径每次提交
E2E 测试Playwright用户关键操作路径内部逻辑细节CI / PR

三层并不是都需要写满。优先保证单元测试的覆盖密度,对纯逻辑代码而言,单测的反馈最快、成本最低、价值最高。集成测试和 E2E 聚焦在真正重要的业务路径上,不做无意义的覆盖率堆砌。

单元测试

针对没有外部依赖的最小单元:纯函数、自定义 hooks、工具方法、状态计算逻辑。

写单测的核心原则:描述行为,而非实现。测试用例的标题应该是一句对行为的陈述,而不是对代码路径的描述。

// 工具函数单测示例
describe('toArray', () => {
  it('null 应返回空数组', () => {
    expect(toArray(null)).toEqual([]);
  });

  it('单个值应包裹为数组', () => {
    expect(toArray(1)).toEqual([1]);
  });

  it('数组应原样返回', () => {
    const arr = [1, 2, 3];
    expect(toArray(arr)).toBe(arr);
  });
});
// React hook 单测示例(使用 renderHook)
describe('useLoading', () => {
  it('默认 loading 应为 false', () => {
    const { result } = renderHook(() => useLoading());
    expect(result.current.loading).toBe(false);
  });

  it('startLoading 应设置为 true', () => {
    const { result } = renderHook(() => useLoading());
    act(() => result.current.startLoading());
    expect(result.current.loading).toBe(true);
  });
});

集成测试

针对跨模块协作边界:HTTP 请求层与业务逻辑的交互、状态管理与副作用的联动、依赖外部服务的模块。

集成测试中推荐使用 MSW(Mock Service Worker)来拦截网络请求,而不是 mock 函数——这样测试的是真实的 fetch/axios 调用路径,而非 mock 的返回值。

// MSW 集成测试示例(拦截 API,验证业务逻辑)
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  http.get('/api/users', () => {
    return HttpResponse.json([{ id: 1, name: 'Alice' }]);
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

it('应正确加载用户列表', async () => {
  const users = await fetchUsers();
  expect(users).toHaveLength(1);
  expect(users[0].name).toBe('Alice');
});

E2E 测试

针对用户视角的关键路径:登录流程、权限跳转、核心业务操作(表单提交、数据增删改查)。

E2E 不追求覆盖率,只覆盖如果这条路径挂了,用户会立刻感知到的流程

// Playwright E2E 示例
import { test, expect } from '@playwright/test';

test('用户应能正常登录并进入首页', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name=username]', 'admin');
  await page.fill('[name=password]', '123456');
  await page.click('[type=submit]');
  await expect(page).toHaveURL('/home');
});

本项目当前方案

工具链

职责工具说明
单元测试 / 集成测试Vitest与 Vite 同生态,TS 零配置,速度极快
React hook 测试@testing-library/reactrenderHook + act
覆盖率报告@vitest/coverage-v8通过 pnpm test:coverage 生成
API Mock(待接入)MSW已安装,可用于集成测试

配置架构

vitest.config.ts                        ← 根配置,统一管理所有 packages 的测试
packages/@skyroc/config/vitest          ← 共享配置(provider、exclude、环境等)
packages/hooks/vitest.config.ts         ← 子包配置(继承共享配置)
packages/@core/*/vitest.config.ts       ← 同上

根配置通过 glob 模式自动发现各包下的测试文件,子包只需在 __tests__/ 目录下新增 .test.ts 文件即可被收录,无需额外注册。

测试文件约定

packages/@core/utils/
├── src/
│   └── array.ts
└── __tests__/
    └── array.test.ts       ← 与 src 文件一一对应

常用命令

# 运行全部测试
pnpm test

# 运行并生成覆盖率报告
pnpm test:coverage

# watch 模式(开发时使用)
pnpm test --watch

已有测试覆盖包

覆盖内容
packages/@core/utils数组、对象、日期、加密、正则、存储、DOM 工具
packages/@core/colorOKLCH 调色板、Ant Design 色板算法
packages/@core/stateJotai atom、storage registry、global store
packages/@core/axios请求配置、类型守卫、选项处理
packages/@core/service请求创建、错误处理、状态共享
packages/@core/scheduler任务调度、任务中心
packages/hooksloading、countdown、copy、array、store 等 hooks

扩展方向

组件测试

packages/web/ui 的 UI 组件目前没有测试覆盖。对于有复杂交互逻辑的组件(受控/非受控切换、条件渲染、事件回调),可以用 React Testing Library 补充:

import { render, screen, fireEvent } from '@testing-library/react';

describe('Button', () => {
  it('点击后应触发 onClick 回调', () => {
    const onClick = vi.fn();
    render(<Button onClick={onClick}>确认</Button>);
    fireEvent.click(screen.getByText('确认'));
    expect(onClick).toHaveBeenCalledOnce();
  });
});

API Mock 集成测试

MSW 已安装,可直接在 apps/admin 的集成测试中接入,拦截真实接口、模拟各种响应场景(正常、超时、鉴权失败)。

E2E 测试

引入 Playwright 后,推荐优先覆盖:

  • 登录 / 登出流程
  • 动态路由权限跳转
  • 表格数据的增删改查操作

新包接入测试的流程

  1. 在包目录下创建 vitest.config.ts,继承共享配置
  2. 创建 vitest.setup.ts(如有需要)
  3. __tests__/ 下新增 *.test.ts 文件
  4. 在根 vitest.config.tsTESTED_PACKAGES 数组中加入该包路径(用于覆盖率收录)
  5. 在根 vitest.config.tsSETUP_FILES 中注册 setup 文件(如有)

On this page