Files
novalon-website/docs/plans/2026-03-10-test-coverage-improvement-plan.md
T
张翔 b207bfa7af feat: 增加测试覆盖率并优化代码质量
test: 添加单元测试和端到端测试
refactor: 重构登录页面和上传模块
ci: 更新测试覆盖率阈值至42%
build: 添加测试相关依赖
docs: 更新测试文档
style: 修复代码格式问题
2026-03-11 11:14:37 +08:00

1324 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 测试覆盖率提升实施计划
> **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%
- ❌ 管理后台APIapp/api/admin/*):0%
- ❌ 特效组件(components/effects/*):0%
---
## 🎯 目标
**Phase 11-2周):** 提升覆盖率到50%
**Phase 21-2周):** 提升覆盖率到70%
**Phase 31周):** 建立长期质量保障机制
---
## Phase 1: 页面组件测试(提升到50%)
### Task 1: 首页页面组件测试
**Files:**
- Create: `src/app/(marketing)/page.test.tsx`
- Test: `src/app/(marketing)/page.tsx`
**Step 1: 查看首页页面源码**
Run:
```bash
cat src/app/\(marketing\)/page.tsx | head -100
```
Expected: 显示首页页面结构和功能
**Step 2: 编写首页页面测试**
Create: `src/app/(marketing)/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
cat src/app/\(marketing\)/about/page.tsx | head -100
```
Expected: 显示关于我们页面结构和功能
**Step 2: 编写关于我们页面测试**
Create: `src/app/(marketing)/about/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/about/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
cat src/app/\(marketing\)/products/page.tsx | head -100
```
Expected: 显示产品页面结构和功能
**Step 2: 编写产品页面测试**
Create: `src/app/(marketing)/products/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/products/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
cat src/app/\(marketing\)/services/page.tsx | head -100
```
Expected: 显示服务页面结构和功能
**Step 2: 编写服务页面测试**
Create: `src/app/(marketing)/services/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/services/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
cat src/app/\(marketing\)/contact/page.tsx | head -100
```
Expected: 显示联系页面结构和功能
**Step 2: 编写联系页面测试**
Create: `src/app/(marketing)/contact/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/contact/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
cat src/app/\(marketing\)/news/page.tsx | head -100
```
Expected: 显示新闻页面结构和功能
**Step 2: 编写新闻页面测试**
Create: `src/app/(marketing)/news/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/news/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
cat src/app/\(marketing\)/cases/page.tsx | head -100
```
Expected: 显示案例页面结构和功能
**Step 2: 编写案例页面测试**
Create: `src/app/(marketing)/cases/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/\(marketing\)/cases/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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:
```bash
npm run test:unit -- --coverage --coverageReporters=text-summary
```
Expected: 覆盖率达到50%以上
**Step 2: 如果未达标,补充缺失测试**
根据覆盖率报告,补充缺失的测试用例。
**Step 3: 提交Phase 1完成标记**
```bash
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:
```bash
cat src/app/admin/login/page.tsx | head -100
```
Expected: 显示登录页面结构和功能
**Step 2: 编写登录页面测试**
Create: `src/app/admin/login/page.test.tsx`
```typescript
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:
```bash
npm run test:unit -- src/app/admin/login/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
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`
```javascript
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
}
```
**Step 2: 配置CI/CD质量门禁**
Modify: `.woodpecker/quality-gate.yml`
```yaml
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:
```bash
npm run test:unit -- --coverage
```
Expected: 如果覆盖率低于70%,测试失败
**Step 4: 提交配置**
```bash
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`
```markdown
# 测试规范指南
## 测试策略
### 测试金字塔
- 单元测试: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. 测试隔离
每个测试应该独立运行,不依赖其他测试:
```typescript
beforeEach(() => {
jest.clearAllMocks();
});
```
### 3. 测试可读性
- 使用有意义的变量名
- 避免魔法数字
- 添加必要的注释
### 4. 测试覆盖率
- 关注代码路径覆盖
- 测试边界条件
- 测试错误处理
## 运行测试
### 单元测试
```bash
npm run test:unit
```
### 覆盖率报告
```bash
npm run test:unit -- --coverage
```
### E2E测试
```bash
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`
```markdown
# 测试培训材料
## 培训目标
- 理解测试驱动开发(TDD
- 掌握Jest和React Testing Library
- 编写高质量测试代码
## 培训内容
### 第一部分:测试基础
1. 测试的重要性
2. 测试类型(单元、集成、E2E
3. 测试金字塔
### 第二部分:Jest基础
1. Jest配置
2. 基本APIdescribe, 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: 提交培训材料**
```bash
git add docs/TESTING_TRAINING.md
git commit -m "docs: add testing training materials"
```
---
### Task 19: 最终验证
**Step 1: 运行完整测试套件**
Run:
```bash
npm run test:unit -- --coverage --coverageReporters=text-summary
```
Expected: 覆盖率达到70%以上
**Step 2: 运行CI/CD流水线**
Run:
```bash
npm run lint && npm run typecheck && npm run test:unit
```
Expected: 所有检查通过
**Step 3: 生成最终报告**
Run:
```bash
npm run test:unit -- --coverage --coverageReporters=html
```
Expected: 生成HTML覆盖率报告
**Step 4: 提交最终完成标记**
```bash
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%
- 测试规范文档:完整
- 团队培训材料:完整
---
## 🎯 成功标准
1. ✅ 测试覆盖率达到70%以上
2. ✅ 所有测试通过率100%
3. ✅ CI/CD质量门禁配置完成
4. ✅ 测试规范文档完整
5. ✅ 团队培训材料完整
---
**计划创建完成!**