931 字
5 分钟
自动化测试实战:单元测试、集成测试到 E2E 测试的完整方案

自动化测试实战:单元测试、集成测试到 E2E 测试的完整方案#

🧪 前言:自动化测试是保证代码质量的重要手段。完善的测试体系可以提高开发效率、减少 Bug、增强重构信心。这篇文章将系统讲解前端自动化测试的各种策略和工具。


一、测试金字塔#

测试金字塔描述了不同类型测试的理想比例:

/\
/ \ E2E 测试(少,慢,贵)
/ \ 5%
/______\
/ \ 集成测试(中,中,中)
/ \ 20%
/____________\
/ \ 单元测试(多,快,便宜)
/________________\ 75%

原则:

  • 单元测试要多而快
  • E2E 测试要少而精
  • 测试应该可靠、可维护

二、单元测试#

2.1 Jest 基础#

sum.js
export function sum(a, b) {
return a + b;
}
// sum.test.js
import { sum } from './sum';
describe('sum', () => {
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('adds negative numbers', () => {
expect(sum(-1, -2)).toBe(-3);
});
});

2.2 Vue 组件测试#

Counter.spec.js
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter', () => {
test('increments count when button is clicked', async () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain('Count: 0');
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('Count: 1');
});
test('renders props correctly', () => {
const wrapper = mount(Counter, {
props: {
initialCount: 10
}
});
expect(wrapper.text()).toContain('Count: 10');
});
});

2.3 React 组件测试#

Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
describe('Counter', () => {
test('renders counter', () => {
render(<Counter />);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
});
test('increments on button click', () => {
render(<Counter />);
fireEvent.click(screen.getByText('Increment'));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
});

2.4 Mock 和 Stub#

// 模拟模块
jest.mock('./api', () => ({
fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: 'John' }))
}));
// 模拟函数
const mockFn = jest.fn();
mockFn.mockReturnValue('mocked value');
// 模拟定时器
jest.useFakeTimers();
test('calls callback after 1 second', () => {
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toBeCalled();
});

三、集成测试#

3.1 API 集成测试#

api.test.js
import request from 'supertest';
import app from './app';
describe('API Integration', () => {
test('GET /users returns list of users', async () => {
const response = await request(app)
.get('/users')
.expect(200);
expect(response.body).toBeInstanceOf(Array);
expect(response.body[0]).toHaveProperty('id');
expect(response.body[0]).toHaveProperty('name');
});
test('POST /users creates a new user', async () => {
const newUser = { name: 'Alice', email: 'alice@example.com' };
const response = await request(app)
.post('/users')
.send(newUser)
.expect(201);
expect(response.body.name).toBe(newUser.name);
expect(response.body).toHaveProperty('id');
});
});

3.2 数据库集成测试#

setupTests.js
beforeAll(async () => {
await db.connect(process.env.TEST_DATABASE_URL);
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
describe('Database Integration', () => {
test('creates and retrieves user', async () => {
const user = await User.create({
name: 'Test User',
email: 'test@example.com'
});
const found = await User.findById(user.id);
expect(found.name).toBe('Test User');
});
});

四、E2E 测试#

4.1 Cypress#

cypress/e2e/login.cy.js
describe('Login', () => {
beforeEach(() => {
cy.visit('/login');
});
it('successfully logs in with valid credentials', () => {
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
});
it('shows error with invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('invalid@example.com');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();
cy.get('[data-testid="error-message"]').should('be.visible');
});
});

4.2 Playwright#

tests/login.spec.js
import { test, expect } from '@playwright/test';
test.describe('Login', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('successfully logs in', async ({ page }) => {
await page.fill('[data-testid="email-input"]', 'user@example.com');
await page.fill('[data-testid="password-input"]', 'password123');
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('[data-testid="welcome-message"]')).toContainText('Welcome');
});
test('visual regression test', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png');
});
});

五、测试最佳实践#

5.1 AAA 模式#

test('calculates discount correctly', () => {
// Arrange(准备)
const price = 100;
const discountRate = 0.2;
// Act(执行)
const result = calculateDiscount(price, discountRate);
// Assert(断言)
expect(result).toBe(80);
});

5.2 测试数据工厂#

factories/user.js
import { faker } from '@faker-js/faker';
export function createUser(overrides = {}) {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
...overrides
};
}
// 使用
test('displays user name', () => {
const user = createUser({ name: 'Test User' });
render(<UserCard user={user} />);
expect(screen.getByText('Test User')).toBeInTheDocument();
});

5.3 测试覆盖率#

// jest.config.js
{
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}

六、CI/CD 集成#

.github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
run: npm run test:integration
- name: Run E2E tests
run: npm run test:e2e
- name: Upload coverage
uses: codecov/codecov-action@v3

七、总结#

自动化测试是软件质量的保障:

  1. 单元测试:测试函数和组件,快速反馈
  2. 集成测试:测试模块间的协作
  3. E2E 测试:模拟真实用户场景

原则:

  • 测试应该是独立的
  • 测试应该是可重复的
  • 测试应该覆盖关键路径
  • 测试应该易于维护

投资测试,就是投资代码质量和开发效率!

自动化测试实战:单元测试、集成测试到 E2E 测试的完整方案
https://www.oferry.com/posts/a88/
作者
晨平安
发布于
2026-02-27
许可协议
CC BY-NC-SA 4.0
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00