Skip to main content

测试指南

本指南介绍项目的测试策略和如何编写测试。

测试类型

1. 构建测试

验证项目能够成功构建:

# 运行构建
pnpm build

# 检查构建输出
ls -la build/

2. 链接检查

检查文档中的链接是否有效:

# 构建时会自动检查内部链接
pnpm build

# 使用 broken-link-checker 检查外部链接
npx broken-link-checker http://localhost:3000 -ro

3. 格式检查

检查代码格式和规范:

# 运行 ESLint
pnpm lint

# 自动修复
pnpm lint:fix

# 检查 Markdown 格式
pnpm format:check

# 自动格式化
pnpm format

测试环境设置

安装测试依赖

pnpm add -D jest @testing-library/react @testing-library/jest-dom

Jest 配置

创建 jest.config.js

module.exports = {
testEnvironment: 'jsdom',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.js'],
moduleNameMapper: {
'^@site/(.*)$': '<rootDir>/$1',
'^@theme/(.*)$': '<rootDir>/node_modules/@docusaurus/theme-classic/src/theme/$1',
},
transform: {
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', {presets: ['@docusaurus/core/lib/babel/preset']}],
},
};

编写测试

组件测试

测试 React 组件:

// src/components/__tests__/CustomButton.test.js
import React from 'react';
import {render, screen, fireEvent} from '@testing-library/react';
import CustomButton from '../CustomButton';

describe('CustomButton', () => {
test('renders button with text', () => {
render(<CustomButton>Click me</CustomButton>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});

test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<CustomButton onClick={handleClick}>Click me</CustomButton>);

fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});

test('applies custom className', () => {
render(<CustomButton className="custom">Click me</CustomButton>);
expect(screen.getByText('Click me')).toHaveClass('custom');
});
});

配置测试

测试配置文件:

// __tests__/config.test.js
const config = require('../docusaurus.config');

describe('Docusaurus Config', () => {
test('has required fields', () => {
expect(config.title).toBeDefined();
expect(config.url).toBeDefined();
expect(config.baseUrl).toBeDefined();
});

test('has correct i18n config', () => {
expect(config.i18n.defaultLocale).toBe('zh-Hans');
expect(config.i18n.locales).toContain('zh-Hans');
expect(config.i18n.locales).toContain('en');
});

test('has navbar items', () => {
expect(config.themeConfig.navbar.items).toBeDefined();
expect(config.themeConfig.navbar.items.length).toBeGreaterThan(0);
});
});

侧边栏测试

测试侧边栏配置:

// __tests__/sidebars.test.js
const sidebars = require('../sidebars');

describe('Sidebars Config', () => {
test('has docsSidebar', () => {
expect(sidebars.docsSidebar).toBeDefined();
});

test('docsSidebar is an array', () => {
expect(Array.isArray(sidebars.docsSidebar)).toBe(true);
});

test('has required categories', () => {
const categories = sidebars.docsSidebar.map(item => item.label);
expect(categories).toContain('快速开始');
expect(categories).toContain('用户指南');
expect(categories).toContain('API 文档');
});
});

文档测试

Markdown 语法检查

使用 markdownlint 检查 Markdown 语法:

# 安装
pnpm add -D markdownlint-cli

# 检查
pnpm markdownlint "docs/**/*.md"

# 配置 .markdownlint.json
{
"default": true,
"MD013": false,
"MD033": false
}

前置元数据验证

验证文档的前置元数据:

// scripts/validate-frontmatter.js
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');

function validateFrontmatter(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const {data} = matter(content);

// 检查必需字段
if (!data.sidebar_position) {
console.error(`Missing sidebar_position in ${filePath}`);
return false;
}

if (!data.title) {
console.error(`Missing title in ${filePath}`);
return false;
}

return true;
}

// 遍历所有文档
const docsDir = path.join(__dirname, '../docs');
// ... 实现遍历逻辑

持续集成

GitHub Actions

创建 .github/workflows/test.yml

name: Test

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run linter
run: pnpm lint

- name: Run tests
run: pnpm test

- name: Build
run: pnpm build

- name: Check links
run: |
pnpm serve &
sleep 5
npx broken-link-checker http://localhost:3000 -ro

性能测试

Lighthouse CI

配置 Lighthouse CI 进行性能测试:

# 安装
pnpm add -D @lhci/cli

# 配置 lighthouserc.js
module.exports = {
ci: {
collect: {
startServerCommand: 'pnpm serve',
url: ['http://localhost:3000'],
},
assert: {
assertions: {
'categories:performance': ['error', {minScore: 0.9}],
'categories:accessibility': ['error', {minScore: 0.9}],
'categories:best-practices': ['error', {minScore: 0.9}],
'categories:seo': ['error', {minScore: 0.9}],
},
},
},
};

# 运行
pnpm lhci autorun

测试最佳实践

1. 测试命名

使用描述性的测试名称:

// ✅ 好的
test('renders error message when API call fails', () => {});

// ❌ 不好的
test('test1', () => {});

2. 测试隔离

每个测试应该独立:

// ✅ 好的
describe('UserList', () => {
beforeEach(() => {
// 每个测试前重置状态
jest.clearAllMocks();
});

test('test 1', () => {});
test('test 2', () => {});
});

3. 测试覆盖率

追求有意义的测试覆盖率:

# 运行测试并生成覆盖率报告
pnpm test --coverage

# 查看覆盖率报告
open coverage/lcov-report/index.html

4. 快照测试

谨慎使用快照测试:

test('renders correctly', () => {
const {container} = render(<MyComponent />);
expect(container.firstChild).toMatchSnapshot();
});

调试测试

使用 console.log

test('debug test', () => {
const result = someFunction();
console.log('Result:', result);
expect(result).toBe(expected);
});

使用 debug

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

test('debug test', () => {
const {debug} = render(<MyComponent />);
debug(); // 打印当前 DOM
});

VS Code 调试

配置 .vscode/launch.json

{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest Debug",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--no-cache"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}

常见问题

测试超时

增加超时时间:

test('long running test', async () => {
// ...
}, 10000); // 10 秒超时

模拟模块

jest.mock('@docusaurus/useDocusaurusContext', () => ({
__esModule: true,
default: () => ({
siteConfig: {
title: 'Test Site',
},
}),
}));

下一步