test: 添加单元测试和端到端测试 refactor: 重构登录页面和上传模块 ci: 更新测试覆盖率阈值至42% build: 添加测试相关依赖 docs: 更新测试文档 style: 修复代码格式问题
31 KiB
测试覆盖率提升实施计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 将测试覆盖率从31.85%提升到70%,确保企业官网零风险上线
Architecture: 采用TDD(测试驱动开发)方法,优先补充核心业务模块测试,建立长期质量保障机制
Tech Stack: Jest + React Testing Library + TypeScript
📊 当前状态分析
当前覆盖率: 31.85%
已覆盖模块:
- ✅ API路由(auth, contact, health):94-100%
- ✅ 核心业务组件(services, products, cases, news, testimonials, insights):100%
- ✅ 工具库(utils, analytics, sanitize, constants):100%
- ✅ 权限系统(permissions):100%
未覆盖模块:
- ❌ 页面组件(app/(marketing)/*):0%
- ❌ 管理后台(app/admin/*):0%
- ❌ 管理后台API(app/api/admin/*):0%
- ❌ 特效组件(components/effects/*):0%
🎯 目标
Phase 1(1-2周): 提升覆盖率到50% Phase 2(1-2周): 提升覆盖率到70% Phase 3(1周): 建立长期质量保障机制
Phase 1: 页面组件测试(提升到50%)
Task 1: 首页页面组件测试
Files:
- Create:
src/app/(marketing)/page.test.tsx - Test:
src/app/(marketing)/page.tsx
Step 1: 查看首页页面源码
Run:
cat src/app/\(marketing\)/page.tsx | head -100
Expected: 显示首页页面结构和功能
Step 2: 编写首页页面测试
Create: src/app/(marketing)/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import HomePage from './page';
describe('HomePage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render home page', () => {
render(<HomePage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render hero section', () => {
render(<HomePage />);
const heroSection = document.querySelector('#hero');
expect(heroSection).toBeInTheDocument();
});
it('should render services section', () => {
render(<HomePage />);
const servicesSection = document.querySelector('#services');
expect(servicesSection).toBeInTheDocument();
});
it('should render products section', () => {
render(<HomePage />);
const productsSection = document.querySelector('#products');
expect(productsSection).toBeInTheDocument();
});
it('should render cases section', () => {
render(<HomePage />);
const casesSection = document.querySelector('#cases');
expect(casesSection).toBeInTheDocument();
});
it('should render news section', () => {
render(<HomePage />);
const newsSection = document.querySelector('#news');
expect(newsSection).toBeInTheDocument();
});
it('should render contact section', () => {
render(<HomePage />);
const contactSection = document.querySelector('#contact');
expect(contactSection).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<HomePage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<HomePage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
describe('SEO', () => {
it('should have meta description', () => {
render(<HomePage />);
const metaDescription = document.querySelector('meta[name="description"]');
expect(metaDescription).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/page.test.tsx
git commit -m "test: add home page component tests"
Task 2: 关于我们页面组件测试
Files:
- Create:
src/app/(marketing)/about/page.test.tsx - Test:
src/app/(marketing)/about/page.tsx
Step 1: 查看关于我们页面源码
Run:
cat src/app/\(marketing\)/about/page.tsx | head -100
Expected: 显示关于我们页面结构和功能
Step 2: 编写关于我们页面测试
Create: src/app/(marketing)/about/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import AboutPage from './page';
describe('AboutPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render about page', () => {
render(<AboutPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render company introduction', () => {
render(<AboutPage />);
const intro = screen.getByText(/关于我们/i);
expect(intro).toBeInTheDocument();
});
it('should render company history', () => {
render(<AboutPage />);
const history = screen.getByText(/发展历程/i);
expect(history).toBeInTheDocument();
});
it('should render company culture', () => {
render(<AboutPage />);
const culture = screen.getByText(/企业文化/i);
expect(culture).toBeInTheDocument();
});
it('should render team members', () => {
render(<AboutPage />);
const team = screen.getByText(/团队/i);
expect(team).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<AboutPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<AboutPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/about/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/about/page.test.tsx
git commit -m "test: add about page component tests"
Task 3: 产品页面组件测试
Files:
- Create:
src/app/(marketing)/products/page.test.tsx - Test:
src/app/(marketing)/products/page.tsx
Step 1: 查看产品页面源码
Run:
cat src/app/\(marketing\)/products/page.tsx | head -100
Expected: 显示产品页面结构和功能
Step 2: 编写产品页面测试
Create: src/app/(marketing)/products/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import ProductsPage from './page';
describe('ProductsPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render products page', () => {
render(<ProductsPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render products list', () => {
render(<ProductsPage />);
const productsList = screen.getByRole('list');
expect(productsList).toBeInTheDocument();
});
it('should render product cards', () => {
render(<ProductsPage />);
const productCards = screen.getAllByRole('listitem');
expect(productCards.length).toBeGreaterThan(0);
});
it('should render product categories', () => {
render(<ProductsPage />);
const categories = screen.getByText(/产品分类/i);
expect(categories).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have product detail links', () => {
render(<ProductsPage />);
const links = screen.getAllByRole('link');
expect(links.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<ProductsPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<ProductsPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/products/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/products/page.test.tsx
git commit -m "test: add products page component tests"
Task 4: 服务页面组件测试
Files:
- Create:
src/app/(marketing)/services/page.test.tsx - Test:
src/app/(marketing)/services/page.tsx
Step 1: 查看服务页面源码
Run:
cat src/app/\(marketing\)/services/page.tsx | head -100
Expected: 显示服务页面结构和功能
Step 2: 编写服务页面测试
Create: src/app/(marketing)/services/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import ServicesPage from './page';
describe('ServicesPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render services page', () => {
render(<ServicesPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render services list', () => {
render(<ServicesPage />);
const servicesList = screen.getByRole('list');
expect(servicesList).toBeInTheDocument();
});
it('should render service cards', () => {
render(<ServicesPage />);
const serviceCards = screen.getAllByRole('listitem');
expect(serviceCards.length).toBeGreaterThan(0);
});
it('should render service categories', () => {
render(<ServicesPage />);
const categories = screen.getByText(/服务分类/i);
expect(categories).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have service detail links', () => {
render(<ServicesPage />);
const links = screen.getAllByRole('link');
expect(links.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<ServicesPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<ServicesPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/services/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/services/page.test.tsx
git commit -m "test: add services page component tests"
Task 5: 联系页面组件测试
Files:
- Create:
src/app/(marketing)/contact/page.test.tsx - Test:
src/app/(marketing)/contact/page.tsx
Step 1: 查看联系页面源码
Run:
cat src/app/\(marketing\)/contact/page.tsx | head -100
Expected: 显示联系页面结构和功能
Step 2: 编写联系页面测试
Create: src/app/(marketing)/contact/page.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import ContactPage from './page';
describe('ContactPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render contact page', () => {
render(<ContactPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render contact form', () => {
render(<ContactPage />);
const form = screen.getByRole('form');
expect(form).toBeInTheDocument();
});
it('should render name input', () => {
render(<ContactPage />);
const nameInput = screen.getByLabelText(/姓名/i);
expect(nameInput).toBeInTheDocument();
});
it('should render email input', () => {
render(<ContactPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
expect(emailInput).toBeInTheDocument();
});
it('should render message textarea', () => {
render(<ContactPage />);
const messageTextarea = screen.getByLabelText(/留言/i);
expect(messageTextarea).toBeInTheDocument();
});
it('should render submit button', () => {
render(<ContactPage />);
const submitButton = screen.getByRole('button', { name: /提交/i });
expect(submitButton).toBeInTheDocument();
});
});
describe('Form Validation', () => {
it('should show error for empty name', async () => {
render(<ContactPage />);
const submitButton = screen.getByRole('button', { name: /提交/i });
fireEvent.click(submitButton);
await waitFor(() => {
expect(screen.getByText(/请输入姓名/i)).toBeInTheDocument();
});
});
it('should show error for invalid email', async () => {
render(<ContactPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
const submitButton = screen.getByRole('button', { name: /提交/i });
fireEvent.click(submitButton);
await waitFor(() => {
expect(screen.getByText(/请输入有效的邮箱/i)).toBeInTheDocument();
});
});
});
describe('Form Submission', () => {
it('should submit form successfully', async () => {
render(<ContactPage />);
const nameInput = screen.getByLabelText(/姓名/i);
const emailInput = screen.getByLabelText(/邮箱/i);
const messageTextarea = screen.getByLabelText(/留言/i);
const submitButton = screen.getByRole('button', { name: /提交/i });
fireEvent.change(nameInput, { target: { value: '张三' } });
fireEvent.change(emailInput, { target: { value: 'zhangsan@example.com' } });
fireEvent.change(messageTextarea, { target: { value: '这是一条测试留言' } });
fireEvent.click(submitButton);
await waitFor(() => {
expect(screen.getByText(/提交成功/i)).toBeInTheDocument();
});
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<ContactPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<ContactPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
it('should have accessible form labels', () => {
render(<ContactPage />);
const nameInput = screen.getByLabelText(/姓名/i);
const emailInput = screen.getByLabelText(/邮箱/i);
const messageTextarea = screen.getByLabelText(/留言/i);
expect(nameInput).toHaveAttribute('aria-label');
expect(emailInput).toHaveAttribute('aria-label');
expect(messageTextarea).toHaveAttribute('aria-label');
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/contact/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/contact/page.test.tsx
git commit -m "test: add contact page component tests"
Task 6: 新闻页面组件测试
Files:
- Create:
src/app/(marketing)/news/page.test.tsx - Test:
src/app/(marketing)/news/page.tsx
Step 1: 查看新闻页面源码
Run:
cat src/app/\(marketing\)/news/page.tsx | head -100
Expected: 显示新闻页面结构和功能
Step 2: 编写新闻页面测试
Create: src/app/(marketing)/news/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import NewsPage from './page';
describe('NewsPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render news page', () => {
render(<NewsPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render news list', () => {
render(<NewsPage />);
const newsList = screen.getByRole('list');
expect(newsList).toBeInTheDocument();
});
it('should render news cards', () => {
render(<NewsPage />);
const newsCards = screen.getAllByRole('listitem');
expect(newsCards.length).toBeGreaterThan(0);
});
it('should render news categories', () => {
render(<NewsPage />);
const categories = screen.getByText(/新闻分类/i);
expect(categories).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have news detail links', () => {
render(<NewsPage />);
const links = screen.getAllByRole('link');
expect(links.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<NewsPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<NewsPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/news/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/news/page.test.tsx
git commit -m "test: add news page component tests"
Task 7: 案例页面组件测试
Files:
- Create:
src/app/(marketing)/cases/page.test.tsx - Test:
src/app/(marketing)/cases/page.tsx
Step 1: 查看案例页面源码
Run:
cat src/app/\(marketing\)/cases/page.tsx | head -100
Expected: 显示案例页面结构和功能
Step 2: 编写案例页面测试
Create: src/app/(marketing)/cases/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import CasesPage from './page';
describe('CasesPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render cases page', () => {
render(<CasesPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render cases list', () => {
render(<CasesPage />);
const casesList = screen.getByRole('list');
expect(casesList).toBeInTheDocument();
});
it('should render case cards', () => {
render(<CasesPage />);
const caseCards = screen.getAllByRole('listitem');
expect(caseCards.length).toBeGreaterThan(0);
});
it('should render case categories', () => {
render(<CasesPage />);
const categories = screen.getByText(/案例分类/i);
expect(categories).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have case detail links', () => {
render(<CasesPage />);
const links = screen.getAllByRole('link');
expect(links.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<CasesPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<CasesPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/cases/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/cases/page.test.tsx
git commit -m "test: add cases page component tests"
Task 8: 验证Phase 1覆盖率
Step 1: 运行测试覆盖率检查
Run:
npm run test:unit -- --coverage --coverageReporters=text-summary
Expected: 覆盖率达到50%以上
Step 2: 如果未达标,补充缺失测试
根据覆盖率报告,补充缺失的测试用例。
Step 3: 提交Phase 1完成标记
git add .
git commit -m "feat: complete Phase 1 - test coverage reaches 50%"
Phase 2: 管理后台测试(提升到70%)
Task 9: 管理后台登录页面测试
Files:
- Create:
src/app/admin/login/page.test.tsx - Test:
src/app/admin/login/page.tsx
Step 1: 查看登录页面源码
Run:
cat src/app/admin/login/page.tsx | head -100
Expected: 显示登录页面结构和功能
Step 2: 编写登录页面测试
Create: src/app/admin/login/page.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import LoginPage from './page';
describe('LoginPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render login page', () => {
render(<LoginPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render login form', () => {
render(<LoginPage />);
const form = screen.getByRole('form');
expect(form).toBeInTheDocument();
});
it('should render email input', () => {
render(<LoginPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
expect(emailInput).toBeInTheDocument();
});
it('should render password input', () => {
render(<LoginPage />);
const passwordInput = screen.getByLabelText(/密码/i);
expect(passwordInput).toBeInTheDocument();
});
it('should render login button', () => {
render(<LoginPage />);
const loginButton = screen.getByRole('button', { name: /登录/i });
expect(loginButton).toBeInTheDocument();
});
});
describe('Form Validation', () => {
it('should show error for empty email', async () => {
render(<LoginPage />);
const loginButton = screen.getByRole('button', { name: /登录/i });
fireEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText(/请输入邮箱/i)).toBeInTheDocument();
});
});
it('should show error for invalid email', async () => {
render(<LoginPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
const loginButton = screen.getByRole('button', { name: /登录/i });
fireEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText(/请输入有效的邮箱/i)).toBeInTheDocument();
});
});
it('should show error for empty password', async () => {
render(<LoginPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
fireEvent.change(emailInput, { target: { value: 'admin@example.com' } });
const loginButton = screen.getByRole('button', { name: /登录/i });
fireEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText(/请输入密码/i)).toBeInTheDocument();
});
});
});
describe('Login Flow', () => {
it('should login successfully with valid credentials', async () => {
render(<LoginPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
const passwordInput = screen.getByLabelText(/密码/i);
const loginButton = screen.getByRole('button', { name: /登录/i });
fireEvent.change(emailInput, { target: { value: 'admin@example.com' } });
fireEvent.change(passwordInput, { target: { value: 'password123' } });
fireEvent.click(loginButton);
await waitFor(() => {
expect(window.location.pathname).toBe('/admin');
});
});
it('should show error for invalid credentials', async () => {
render(<LoginPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
const passwordInput = screen.getByLabelText(/密码/i);
const loginButton = screen.getByRole('button', { name: /登录/i });
fireEvent.change(emailInput, { target: { value: 'wrong@example.com' } });
fireEvent.change(passwordInput, { target: { value: 'wrongpassword' } });
fireEvent.click(loginButton);
await waitFor(() => {
expect(screen.getByText(/邮箱或密码错误/i)).toBeInTheDocument();
});
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<LoginPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<LoginPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
it('should have accessible form labels', () => {
render(<LoginPage />);
const emailInput = screen.getByLabelText(/邮箱/i);
const passwordInput = screen.getByLabelText(/密码/i);
expect(emailInput).toHaveAttribute('aria-label');
expect(passwordInput).toHaveAttribute('aria-label');
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/admin/login/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/admin/login/page.test.tsx
git commit -m "test: add admin login page tests"
Task 10-15: 管理后台其他页面测试
(类似Task 9,为管理后台的其他页面添加测试)
- Task 10: 内容管理页面测试
- Task 11: 用户管理页面测试
- Task 12: 配置中心页面测试
- Task 13: 审计日志页面测试
- Task 14: 管理后台API测试
- Task 15: 验证Phase 2覆盖率
Phase 3: 建立长期质量保障机制
Task 16: 配置覆盖率门禁
Files:
- Modify:
jest.config.js - Modify:
.woodpecker/quality-gate.yml
Step 1: 配置Jest覆盖率门禁
Modify: jest.config.js
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
}
Step 2: 配置CI/CD质量门禁
Modify: .woodpecker/quality-gate.yml
test:unit:
image: node:20-alpine
commands:
- npm ci
- npm run test:unit -- --coverage --coverageThreshold='{"global":{"branches":70,"functions":70,"lines":70,"statements":70}}'
when:
event: [push, pull_request]
Step 3: 验证配置
Run:
npm run test:unit -- --coverage
Expected: 如果覆盖率低于70%,测试失败
Step 4: 提交配置
git add jest.config.js .woodpecker/quality-gate.yml
git commit -m "feat: add coverage threshold gate (70%)"
Task 17: 建立测试规范文档
Files:
- Create:
docs/TESTING_GUIDELINES.md
Step 1: 创建测试规范文档
Create: docs/TESTING_GUIDELINES.md
# 测试规范指南
## 测试策略
### 测试金字塔
- 单元测试:70%(核心业务逻辑)
- 集成测试:20%(API集成)
- E2E测试:10%(关键用户流程)
### 覆盖率要求
- 全局覆盖率:≥70%
- 核心模块覆盖率:≥90%
- 新功能覆盖率:≥80%
## 测试命名规范
### 测试文件命名
- 单元测试:`*.test.ts` 或 `*.test.tsx`
- 集成测试:`*.integration.test.ts`
- E2E测试:`*.spec.ts`
### 测试用例命名
使用描述性命名,遵循"应该..."模式:
```typescript
describe('ComponentName', () => {
describe('Feature', () => {
it('should do something when condition', () => {
// test code
});
});
});
测试最佳实践
1. AAA模式
- Arrange(准备):设置测试数据
- Act(执行):执行被测试的代码
- Assert(断言):验证结果
2. 测试隔离
每个测试应该独立运行,不依赖其他测试:
beforeEach(() => {
jest.clearAllMocks();
});
3. 测试可读性
- 使用有意义的变量名
- 避免魔法数字
- 添加必要的注释
4. 测试覆盖率
- 关注代码路径覆盖
- 测试边界条件
- 测试错误处理
运行测试
单元测试
npm run test:unit
覆盖率报告
npm run test:unit -- --coverage
E2E测试
npm run test:e2e
持续集成
所有测试在CI/CD流水线中自动运行,覆盖率低于70%将导致构建失败。
**Step 2: 提交文档**
```bash
git add docs/TESTING_GUIDELINES.md
git commit -m "docs: add testing guidelines"
Task 18: 团队培训材料
Files:
- Create:
docs/TESTING_TRAINING.md
Step 1: 创建培训材料
Create: docs/TESTING_TRAINING.md
# 测试培训材料
## 培训目标
- 理解测试驱动开发(TDD)
- 掌握Jest和React Testing Library
- 编写高质量测试代码
## 培训内容
### 第一部分:测试基础
1. 测试的重要性
2. 测试类型(单元、集成、E2E)
3. 测试金字塔
### 第二部分:Jest基础
1. Jest配置
2. 基本API(describe, it, expect)
3. Mock和Spy
4. 异步测试
### 第三部分:React Testing Library
1. 渲染组件
2. 查询元素
3. 用户交互
4. 异步操作
### 第四部分:最佳实践
1. AAA模式
2. 测试隔离
3. 可读性
4. 覆盖率
## 实践练习
### 练习1:编写第一个测试
为简单的工具函数编写测试。
### 练习2:组件测试
为React组件编写测试。
### 练习3:异步测试
测试异步操作(API调用)。
## 参考资料
- [Jest官方文档](https://jestjs.io/)
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
- [测试最佳实践](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
Step 2: 提交培训材料
git add docs/TESTING_TRAINING.md
git commit -m "docs: add testing training materials"
Task 19: 最终验证
Step 1: 运行完整测试套件
Run:
npm run test:unit -- --coverage --coverageReporters=text-summary
Expected: 覆盖率达到70%以上
Step 2: 运行CI/CD流水线
Run:
npm run lint && npm run typecheck && npm run test:unit
Expected: 所有检查通过
Step 3: 生成最终报告
Run:
npm run test:unit -- --coverage --coverageReporters=html
Expected: 生成HTML覆盖率报告
Step 4: 提交最终完成标记
git add .
git commit -m "feat: complete test coverage improvement to 70%"
git tag v1.0.0-test-coverage-70
📊 预期成果
Phase 1完成后:
- 测试覆盖率:≥50%
- 页面组件测试:100%覆盖
- 核心业务功能:100%覆盖
Phase 2完成后:
- 测试覆盖率:≥70%
- 管理后台测试:100%覆盖
- API路由测试:100%覆盖
Phase 3完成后:
- 覆盖率门禁:≥70%
- 测试规范文档:完整
- 团队培训材料:完整
🎯 成功标准
- ✅ 测试覆盖率达到70%以上
- ✅ 所有测试通过率100%
- ✅ CI/CD质量门禁配置完成
- ✅ 测试规范文档完整
- ✅ 团队培训材料完整
计划创建完成!