b207bfa7af
test: 添加单元测试和端到端测试 refactor: 重构登录页面和上传模块 ci: 更新测试覆盖率阈值至42% build: 添加测试相关依赖 docs: 更新测试文档 style: 修复代码格式问题
1324 lines
31 KiB
Markdown
1324 lines
31 KiB
Markdown
# 测试覆盖率提升实施计划
|
||
|
||
> **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:
|
||
```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. 基本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: 提交培训材料**
|
||
|
||
```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. ✅ 团队培训材料完整
|
||
|
||
---
|
||
|
||
**计划创建完成!**
|