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

31 KiB
Raw Blame History

测试覆盖率提升实施计划

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:

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. 基本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: 提交培训材料

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%
  • 测试规范文档:完整
  • 团队培训材料:完整

🎯 成功标准

  1. 测试覆盖率达到70%以上
  2. 所有测试通过率100%
  3. CI/CD质量门禁配置完成
  4. 测试规范文档完整
  5. 团队培训材料完整

计划创建完成!