931 字
5 分钟
自动化测试实战:单元测试、集成测试到 E2E 测试的完整方案
自动化测试实战:单元测试、集成测试到 E2E 测试的完整方案
🧪 前言:自动化测试是保证代码质量的重要手段。完善的测试体系可以提高开发效率、减少 Bug、增强重构信心。这篇文章将系统讲解前端自动化测试的各种策略和工具。
一、测试金字塔
测试金字塔描述了不同类型测试的理想比例:
/\ / \ E2E 测试(少,慢,贵) / \ 5% /______\ / \ 集成测试(中,中,中) / \ 20% /____________\ / \ 单元测试(多,快,便宜)/________________\ 75%原则:
- 单元测试要多而快
- E2E 测试要少而精
- 测试应该可靠、可维护
二、单元测试
2.1 Jest 基础
export function sum(a, b) { return a + b;}
// sum.test.jsimport { 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 组件测试
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 组件测试
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 集成测试
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 数据库集成测试
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
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
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 测试数据工厂
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 集成
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七、总结
自动化测试是软件质量的保障:
- 单元测试:测试函数和组件,快速反馈
- 集成测试:测试模块间的协作
- E2E 测试:模拟真实用户场景
原则:
- 测试应该是独立的
- 测试应该是可重复的
- 测试应该覆盖关键路径
- 测试应该易于维护
投资测试,就是投资代码质量和开发效率!
自动化测试实战:单元测试、集成测试到 E2E 测试的完整方案
https://www.oferry.com/posts/a88/