test: 添加单元测试和端到端测试 refactor: 重构登录页面和上传模块 ci: 更新测试覆盖率阈值至42% build: 添加测试相关依赖 docs: 更新测试文档 style: 修复代码格式问题
23 KiB
全模块测试覆盖实施计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 补充所有未覆盖模块的测试,将测试覆盖率从42.57%提升到70%
Architecture: 采用TDD方法,优先补充核心业务模块测试,确保所有页面和API功能正常
Tech Stack: Jest + React Testing Library + TypeScript
📊 当前状态
当前覆盖率: 42.57%
未覆盖模块(0%):
- 页面详情页:cases/[id], news/[slug], products/[id], services/[id], solutions
- 管理后台:admin主页, content/[id], logs
- 管理后台API:config, content/[id], logs, upload, users/[id]
- 其他页面:privacy, terms, preview/effects
- 组件:admin, analytics, effects, examples, seo
🎯 目标
覆盖率目标: 70%
任务总数: 25个
预计时间: 2-3周
Phase 1: 页面详情页测试(提升到55%)
Task 1: 案例详情页测试
Files:
- Create:
src/app/(marketing)/cases/[id]/page.test.tsx - Test:
src/app/(marketing)/cases/[id]/page.tsx
Step 1: 查看案例详情页源码
Run:
cat src/app/\(marketing\)/cases/\[id\]/page.tsx | head -100
Expected: 显示案例详情页结构和功能
Step 2: 编写案例详情页测试
Create: src/app/(marketing)/cases/[id]/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import CaseDetailPage from './page';
describe('CaseDetailPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render case detail page', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render case title', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
});
it('should render case content', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const content = screen.getByText(/案例详情/i);
expect(content).toBeInTheDocument();
});
it('should render case images', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const images = screen.getAllByRole('img');
expect(images.length).toBeGreaterThan(0);
});
it('should render related cases', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const relatedCases = screen.getByText(/相关案例/i);
expect(relatedCases).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have back to cases link', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const backLink = screen.getByRole('link', { name: /返回/i });
expect(backLink).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/cases/\[id\]/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/cases/\[id\]/page.test.tsx
git commit -m "test: add case detail page tests"
Task 2: 新闻详情页测试
Files:
- Create:
src/app/(marketing)/news/[slug]/page.test.tsx - Test:
src/app/(marketing)/news/[slug]/page.tsx
Step 1: 查看新闻详情页源码
Run:
cat src/app/\(marketing\)/news/\[slug\]/page.tsx | head -100
Expected: 显示新闻详情页结构和功能
Step 2: 编写新闻详情页测试
Create: src/app/(marketing)/news/[slug]/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import NewsDetailPage from './page';
describe('NewsDetailPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render news detail page', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render news title', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
});
it('should render news content', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const content = screen.getByText(/新闻详情/i);
expect(content).toBeInTheDocument();
});
it('should render news date', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const date = screen.getByText(/\d{4}-\d{2}-\d{2}/);
expect(date).toBeInTheDocument();
});
it('should render news category', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const category = screen.getByText(/分类/i);
expect(category).toBeInTheDocument();
});
it('should render related news', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const relatedNews = screen.getByText(/相关新闻/i);
expect(relatedNews).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have back to news link', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const backLink = screen.getByRole('link', { name: /返回/i });
expect(backLink).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/\(marketing\)/news/\[slug\]/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/\(marketing\)/news/\[slug\]/page.test.tsx
git commit -m "test: add news detail page tests"
Task 3-5: 产品详情页、服务详情页、解决方案页测试
(类似Task 1-2,为产品详情页、服务详情页、解决方案页添加测试)
Phase 2: 管理后台页面测试(提升到60%)
Task 6: 管理后台主页测试
Files:
- Create:
src/app/admin/page.test.tsx - Test:
src/app/admin/page.tsx
Step 1: 查看管理后台主页源码
Run:
cat src/app/admin/page.tsx | head -100
Expected: 显示管理后台主页结构和功能
Step 2: 编写管理后台主页测试
Create: src/app/admin/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import AdminPage from './page';
describe('AdminPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render admin page', () => {
render(<AdminPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render dashboard', () => {
render(<AdminPage />);
const dashboard = screen.getByText(/仪表盘/i);
expect(dashboard).toBeInTheDocument();
});
it('should render statistics', () => {
render(<AdminPage />);
const stats = screen.getByText(/统计/i);
expect(stats).toBeInTheDocument();
});
it('should render quick actions', () => {
render(<AdminPage />);
const quickActions = screen.getByText(/快捷操作/i);
expect(quickActions).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have navigation menu', () => {
render(<AdminPage />);
const nav = screen.getByRole('navigation');
expect(nav).toBeInTheDocument();
});
it('should have content management link', () => {
render(<AdminPage />);
const contentLink = screen.getByRole('link', { name: /内容管理/i });
expect(contentLink).toBeInTheDocument();
});
it('should have user management link', () => {
render(<AdminPage />);
const userLink = screen.getByRole('link', { name: /用户管理/i });
expect(userLink).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<AdminPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<AdminPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/admin/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/admin/page.test.tsx
git commit -m "test: add admin main page tests"
Task 7-8: 内容编辑页、审计日志页测试
(类似Task 6,为内容编辑页、审计日志页添加测试)
Phase 3: 管理后台API测试(提升到65%)
Task 9: 配置管理API测试
Files:
- Create:
src/app/api/admin/config/route.test.ts - Test:
src/app/api/admin/config/route.ts
Step 1: 查看配置管理API源码
Run:
cat src/app/api/admin/config/route.ts | head -100
Expected: 显示配置管理API结构和功能
Step 2: 编写配置管理API测试
Create: src/app/api/admin/config/route.test.ts
import { GET, POST, PUT, DELETE } from './route';
import { NextRequest } from 'next/server';
describe('/api/admin/config', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('GET', () => {
it('should return config list', async () => {
const request = new NextRequest('http://localhost/api/admin/config');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(Array.isArray(data.data)).toBe(true);
});
it('should return config by key', async () => {
const request = new NextRequest('http://localhost/api/admin/config?key=site_name');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.data).toBeDefined();
});
});
describe('POST', () => {
it('should create new config', async () => {
const request = new NextRequest('http://localhost/api/admin/config', {
method: 'POST',
body: JSON.stringify({
key: 'test_key',
value: 'test_value',
category: 'test',
}),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(201);
expect(data.success).toBe(true);
expect(data.data.key).toBe('test_key');
});
it('should reject invalid config', async () => {
const request = new NextRequest('http://localhost/api/admin/config', {
method: 'POST',
body: JSON.stringify({}),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
});
});
describe('PUT', () => {
it('should update config', async () => {
const request = new NextRequest('http://localhost/api/admin/config', {
method: 'PUT',
body: JSON.stringify({
key: 'test_key',
value: 'updated_value',
}),
});
const response = await PUT(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
});
});
describe('DELETE', () => {
it('should delete config', async () => {
const request = new NextRequest('http://localhost/api/admin/config?key=test_key', {
method: 'DELETE',
});
const response = await DELETE(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/api/admin/config/route.test.ts
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/api/admin/config/route.test.ts
git commit -m "test: add config management API tests"
Task 10-13: 内容API、日志API、上传API、用户API测试
(类似Task 9,为其他管理后台API添加测试)
Phase 4: 其他页面测试(提升到68%)
Task 14: 隐私政策页测试
Files:
- Create:
src/app/privacy/page.test.tsx - Test:
src/app/privacy/page.tsx
Step 1: 查看隐私政策页源码
Run:
cat src/app/privacy/page.tsx | head -50
Expected: 显示隐私政策页结构和功能
Step 2: 编写隐私政策页测试
Create: src/app/privacy/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import PrivacyPage from './page';
describe('PrivacyPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render privacy page', () => {
render(<PrivacyPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render privacy title', () => {
render(<PrivacyPage />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent(/隐私政策/i);
});
it('should render privacy content', () => {
render(<PrivacyPage />);
const content = screen.getByText(/隐私/i);
expect(content).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<PrivacyPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<PrivacyPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/privacy/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/privacy/page.test.tsx
git commit -m "test: add privacy page tests"
Task 15: 服务条款页测试
(类似Task 14,为服务条款页添加测试)
Task 16: 特效预览页测试
Files:
- Create:
src/app/preview/effects/page.test.tsx - Test:
src/app/preview/effects/page.tsx
Step 1: 查看特效预览页源码
Run:
cat src/app/preview/effects/page.tsx | head -100
Expected: 显示特效预览页结构和功能
Step 2: 编写特效预览页测试
Create: src/app/preview/effects/page.test.tsx
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import EffectsPreviewPage from './page';
describe('EffectsPreviewPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render effects preview page', () => {
render(<EffectsPreviewPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render effects list', () => {
render(<EffectsPreviewPage />);
const effectsList = screen.getByRole('list');
expect(effectsList).toBeInTheDocument();
});
it('should render effect cards', () => {
render(<EffectsPreviewPage />);
const effectCards = screen.getAllByRole('listitem');
expect(effectCards.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<EffectsPreviewPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/app/preview/effects/page.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/app/preview/effects/page.test.tsx
git commit -m "test: add effects preview page tests"
Phase 5: 组件测试(提升到70%)
Task 17: 管理后台组件测试
Files:
- Create:
src/components/admin/RichTextEditor.test.tsx - Test:
src/components/admin/RichTextEditor.tsx
Step 1: 查看富文本编辑器组件源码
Run:
cat src/components/admin/RichTextEditor.tsx | head -100
Expected: 显示富文本编辑器组件结构和功能
Step 2: 编写富文本编辑器组件测试
Create: src/components/admin/RichTextEditor.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import RichTextEditor from './RichTextEditor';
describe('RichTextEditor', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render editor', () => {
render(<RichTextEditor value="" onChange={() => {}} />);
const editor = screen.getByRole('textbox');
expect(editor).toBeInTheDocument();
});
it('should render toolbar', () => {
render(<RichTextEditor value="" onChange={() => {}} />);
const toolbar = screen.getByRole('toolbar');
expect(toolbar).toBeInTheDocument();
});
it('should render formatting buttons', () => {
render(<RichTextEditor value="" onChange={() => {}} />);
const boldButton = screen.getByRole('button', { name: /粗体/i });
const italicButton = screen.getByRole('button', { name: /斜体/i });
expect(boldButton).toBeInTheDocument();
expect(italicButton).toBeInTheDocument();
});
});
describe('Functionality', () => {
it('should call onChange when content changes', () => {
const handleChange = jest.fn();
render(<RichTextEditor value="" onChange={handleChange} />);
const editor = screen.getByRole('textbox');
fireEvent.change(editor, { target: { value: 'test content' } });
expect(handleChange).toHaveBeenCalledWith('test content');
});
it('should display initial value', () => {
render(<RichTextEditor value="initial content" onChange={() => {}} />);
const editor = screen.getByRole('textbox');
expect(editor).toHaveValue('initial content');
});
});
describe('Accessibility', () => {
it('should have accessible label', () => {
render(<RichTextEditor value="" onChange={() => {}} aria-label="Content editor" />);
const editor = screen.getByLabelText('Content editor');
expect(editor).toBeInTheDocument();
});
});
});
Step 3: 运行测试验证通过
Run:
npm run test:unit -- src/components/admin/RichTextEditor.test.tsx
Expected: 所有测试通过
Step 4: 提交代码
git add src/components/admin/RichTextEditor.test.tsx
git commit -m "test: add rich text editor component tests"
Task 18-21: 其他组件测试
(类似Task 17,为analytics、effects、examples、seo组件添加测试)
Phase 6: 最终验证
Task 22: 验证覆盖率达到70%
Step 1: 运行测试覆盖率检查
Run:
npm run test:unit -- --coverage --coverageReporters=text-summary
Expected: 覆盖率达到70%以上
Step 2: 如果未达标,补充缺失测试
根据覆盖率报告,补充缺失的测试用例。
Task 23: 更新覆盖率门禁
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
unit-tests:
image: node:18-alpine
commands:
- echo "=== Running unit tests with coverage ==="
- npm run test:unit -- --coverage --coverageReporters=json
- |
COVERAGE=$(cat coverage/coverage-summary.json | grep -o '"lines":{"pct":[0-9.]*' | grep -o '[0-9.]*$')
echo "Current coverage: $COVERAGE%"
if [ $(echo "$COVERAGE < 70" | bc -l) -eq 1 ]; then
echo "❌ Coverage $COVERAGE% is below threshold 70%"
exit 1
fi
echo "✅ Coverage $COVERAGE% meets threshold 70%"
Step 3: 提交配置
git add jest.config.js .woodpecker/quality-gate.yml
git commit -m "feat: update coverage threshold gate to 70%"
Task 24: 创建测试规范文档
Files:
- Create:
docs/TESTING_GUIDELINES.md
Step 1: 创建测试规范文档
(参考之前的测试规范文档内容)
Step 2: 提交文档
git add docs/TESTING_GUIDELINES.md
git commit -m "docs: add testing guidelines"
Task 25: 最终验收
Step 1: 运行完整测试套件
Run:
npm run test:unit -- --coverage
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 full module test coverage to 70%"
git tag v1.0.0-test-coverage-70-full
📊 预期成果
Phase 1完成后:
- 测试覆盖率:≥55%
- 页面详情页测试:100%覆盖
Phase 2完成后:
- 测试覆盖率:≥60%
- 管理后台页面测试:100%覆盖
Phase 3完成后:
- 测试覆盖率:≥65%
- 管理后台API测试:100%覆盖
Phase 4完成后:
- 测试覆盖率:≥68%
- 其他页面测试:100%覆盖
Phase 5完成后:
- 测试覆盖率:≥70%
- 组件测试:100%覆盖
🎯 成功标准
- ✅ 测试覆盖率达到70%以上
- ✅ 所有测试通过率100%
- ✅ 所有未覆盖模块测试完成
- ✅ CI/CD质量门禁更新到70%
- ✅ 测试规范文档完整
计划创建完成!