test-automation-expert
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTest Automation Expert
测试自动化专家
Comprehensive testing guidance from unit to E2E. Designs test strategies, implements automation, and optimizes coverage for sustainable quality.
提供从单元测试到E2E测试的全方位测试指导,设计测试策略、实现自动化并优化测试覆盖率,以保障可持续的代码质量。
When to Use
适用场景
Use for:
- Designing test strategy for new projects
- Setting up testing frameworks (Jest, Vitest, Playwright, Cypress, pytest)
- Writing effective unit, integration, and E2E tests
- Optimizing test coverage and eliminating gaps
- Debugging flaky tests
- CI/CD test pipeline configuration
- Test-Driven Development (TDD) guidance
- Mocking strategies and test fixtures
Do NOT use for:
- Manual QA test case writing - this is automation-focused
- Load/performance testing - use performance-engineer skill
- Security testing - use security-auditor skill
- API contract testing only - use backend-architect for API design
适用情况:
- 为新项目设计测试策略
- 搭建测试框架(Jest、Vitest、Playwright、Cypress、pytest)
- 编写高效的单元测试、集成测试和E2E测试
- 优化测试覆盖率并填补测试缺口
- 调试不稳定测试(Flaky Tests)
- 配置CI/CD测试流水线
- 测试驱动开发(TDD)指导
- Mocking策略与测试夹具(Test Fixtures)设计
不适用情况:
- 手动QA测试用例编写(本专家专注于自动化)
- 负载/性能测试(请使用performance-engineer技能)
- 安全测试(请使用security-auditor技能)
- 仅API契约测试(请使用backend-architect进行API设计)
Test Pyramid Philosophy
测试金字塔理念
/\
/ \ E2E Tests (10%)
/----\ - Critical user journeys
/ \ - Cross-browser validation
/--------\
/ \ Integration Tests (20%)
/ \ - API contracts
/--------------\- Component interactions
/ \
/------------------\ Unit Tests (70%)
- Fast, isolated, deterministic
- Business logic validation /\
/ \ E2E Tests (10%)
/----\ - 关键用户流程
/ \ - 跨浏览器验证
/--------\
/ \ Integration Tests (20%)
/ \ - API契约验证
/--------------\- 组件交互测试
/ \
/------------------\ Unit Tests (70%)
- 快速、隔离、可预测
- 业务逻辑验证Distribution Guidelines
分布指南
| Test Type | Percentage | Execution Time | Purpose |
|---|---|---|---|
| Unit | 70% | < 100ms each | Logic validation |
| Integration | 20% | < 1s each | Component contracts |
| E2E | 10% | < 30s each | Critical paths |
| 测试类型 | 占比 | 执行时间 | 目的 |
|---|---|---|---|
| 单元测试 | 70% | < 100ms 每个 | 逻辑验证 |
| 集成测试 | 20% | < 1s 每个 | 组件契约验证 |
| E2E测试 | 10% | < 30s 每个 | 关键流程验证 |
Framework Selection
框架选择
JavaScript/TypeScript
JavaScript/TypeScript
| Framework | Best For | Speed | Config Complexity |
|---|---|---|---|
| Vitest | Vite projects, modern ESM | Fastest | Low |
| Jest | React, established projects | Fast | Medium |
| Playwright | E2E, cross-browser | N/A | Low |
| Cypress | E2E, component testing | N/A | Medium |
| 框架 | 最佳适用场景 | 速度 | 配置复杂度 |
|---|---|---|---|
| Vitest | Vite项目、现代ESM环境 | 最快 | 低 |
| Jest | React项目、成熟项目 | 快 | 中 |
| Playwright | E2E测试、跨浏览器测试 | N/A | 低 |
| Cypress | E2E测试、组件测试 | N/A | 中 |
Python
Python
| Framework | Best For | Speed | Features |
|---|---|---|---|
| pytest | Everything | Fast | Fixtures, plugins |
| unittest | Standard library | Medium | Built-in |
| hypothesis | Property-based | Varies | Generative |
| 框架 | 最佳适用场景 | 速度 | 功能特性 |
|---|---|---|---|
| pytest | 全场景测试 | 快 | 夹具、插件丰富 |
| unittest | 标准库测试 | 中 | 内置原生支持 |
| hypothesis | 属性化测试 | 可变 | 生成式测试用例 |
Decision Tree: Framework Selection
框架选择决策树
New project?
├── Yes → Using Vite?
│ ├── Yes → Vitest
│ └── No → Jest or Vitest (both work)
└── No → What exists?
├── Jest → Keep Jest (migration cost rarely worth it)
├── Mocha → Consider migration to Vitest
└── Nothing → Vitest (modern default)
Need E2E?
├── Cross-browser critical → Playwright
├── Developer experience priority → Cypress
└── Both → Playwright (more flexible)新项目?
├── 是 → 使用Vite?
│ ├── 是 → Vitest
│ └── 否 → Jest或Vitest(两者均可)
└── 否 → 现有框架?
├── Jest → 保留Jest(迁移成本通常不值得)
├── Mocha → 考虑迁移到Vitest
└── 无 → Vitest(现代默认选择)
需要E2E测试?
├── 跨浏览器关键流程 → Playwright
├── 优先开发者体验 → Cypress
└── 两者都需 → Playwright(更灵活)Unit Testing Patterns
单元测试模式
Good Unit Test Anatomy
优质单元测试结构
javascript
describe('UserService', () => {
describe('validateEmail', () => {
// Arrange-Act-Assert pattern
it('should accept valid email formats', () => {
// Arrange
const validEmails = ['user@example.com', 'name+tag@domain.co'];
// Act & Assert
validEmails.forEach(email => {
expect(validateEmail(email)).toBe(true);
});
});
it('should reject invalid email formats', () => {
// Arrange
const invalidEmails = ['invalid', '@missing.com', 'no@tld'];
// Act & Assert
invalidEmails.forEach(email => {
expect(validateEmail(email)).toBe(false);
});
});
// Edge cases explicitly tested
it('should handle empty string', () => {
expect(validateEmail('')).toBe(false);
});
it('should handle null/undefined', () => {
expect(validateEmail(null)).toBe(false);
expect(validateEmail(undefined)).toBe(false);
});
});
});javascript
describe('UserService', () => {
describe('validateEmail', () => {
// 遵循Arrange-Act-Assert模式
it('应接受合法邮箱格式', () => {
// 准备
const validEmails = ['user@example.com', 'name+tag@domain.co'];
// 执行 & 断言
validEmails.forEach(email => {
expect(validateEmail(email)).toBe(true);
});
});
it('应拒绝非法邮箱格式', () => {
// 准备
const invalidEmails = ['invalid', '@missing.com', 'no@tld'];
// 执行 & 断言
invalidEmails.forEach(email => {
expect(validateEmail(email)).toBe(false);
});
});
// 显式测试边缘情况
it('应处理空字符串', () => {
expect(validateEmail('')).toBe(false);
});
it('应处理null/undefined值', () => {
expect(validateEmail(null)).toBe(false);
expect(validateEmail(undefined)).toBe(false);
});
});
});Mocking Strategies
Mocking策略
javascript
// ✅ Good: Mock at boundaries
jest.mock('../services/api', () => ({
fetchUser: jest.fn()
}));
// ✅ Good: Explicit mock setup per test
beforeEach(() => {
fetchUser.mockReset();
});
it('handles user not found', async () => {
fetchUser.mockRejectedValue(new NotFoundError());
await expect(getUser(123)).rejects.toThrow('User not found');
});
// ❌ Bad: Mocking implementation details
jest.mock('../utils/internal-helper'); // Don't mock internalsjavascript
// ✅ 正确:在边界处进行Mock
jest.mock('../services/api', () => ({
fetchUser: jest.fn()
}));
// ✅ 正确:每个测试前显式初始化Mock
beforeEach(() => {
fetchUser.mockReset();
});
it('处理用户不存在的情况', async () => {
fetchUser.mockRejectedValue(new NotFoundError());
await expect(getUser(123)).rejects.toThrow('User not found');
});
// ❌ 错误:Mock实现细节
jest.mock('../utils/internal-helper'); // 不要Mock内部方法Test Isolation Checklist
测试隔离检查表
- Each test can run independently
- No shared mutable state between tests
- Database/API state reset between tests
- No test order dependencies
- Parallel execution safe
- 每个测试可独立运行
- 测试间无共享可变状态
- 数据库/API状态在测试间重置
- 测试无执行顺序依赖
- 支持并行执行
Integration Testing Patterns
集成测试模式
API Integration Test
API集成测试
javascript
describe('POST /api/users', () => {
let app;
let db;
beforeAll(async () => {
db = await createTestDatabase();
app = createApp({ db });
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
it('creates user with valid data', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Test', email: 'test@example.com' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
name: 'Test',
email: 'test@example.com'
});
// Verify side effects
const dbUser = await db.users.findById(response.body.id);
expect(dbUser).toBeDefined();
});
it('rejects duplicate email', async () => {
await db.users.create({ name: 'Existing', email: 'test@example.com' });
await request(app)
.post('/api/users')
.send({ name: 'New', email: 'test@example.com' })
.expect(409);
});
});javascript
describe('POST /api/users', () => {
let app;
let db;
beforeAll(async () => {
db = await createTestDatabase();
app = createApp({ db });
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
});
it('使用合法数据创建用户', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Test', email: 'test@example.com' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
name: 'Test',
email: 'test@example.com'
});
// 验证副作用
const dbUser = await db.users.findById(response.body.id);
expect(dbUser).toBeDefined();
});
it('拒绝重复邮箱', async () => {
await db.users.create({ name: 'Existing', email: 'test@example.com' });
await request(app)
.post('/api/users')
.send({ name: 'New', email: 'test@example.com' })
.expect(409);
});
});Component Integration (React)
组件集成测试(React)
javascript
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';
import { UserProvider } from '../context/UserContext';
describe('UserProfile integration', () => {
it('loads and displays user data', async () => {
render(
<UserProvider>
<UserProfile userId="123" />
</UserProvider>
);
// Verify loading state
expect(screen.getByRole('progressbar')).toBeInTheDocument();
// Wait for data
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
// Verify loaded state
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
});javascript
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';
import { UserProvider } from '../context/UserContext';
describe('UserProfile集成测试', () => {
it('加载并展示用户数据', async () => {
render(
<UserProvider>
<UserProfile userId="123" />
</UserProvider>
);
// 验证加载状态
expect(screen.getByRole('progressbar')).toBeInTheDocument();
// 等待数据加载完成
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
// 验证加载完成状态
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
});E2E Testing Patterns
E2E测试模式
Playwright Best Practices
Playwright最佳实践
javascript
import { test, expect } from '@playwright/test';
test.describe('Checkout Flow', () => {
test.beforeEach(async ({ page }) => {
// Seed test data via API
await page.request.post('/api/test/seed', {
data: { scenario: 'checkout-ready' }
});
});
test('complete purchase with credit card', async ({ page }) => {
await page.goto('/cart');
// Use accessible selectors
await page.getByRole('button', { name: 'Proceed to checkout' }).click();
// Fill payment form
await page.getByLabel('Card number').fill('4242424242424242');
await page.getByLabel('Expiry').fill('12/25');
await page.getByLabel('CVC').fill('123');
// Complete purchase
await page.getByRole('button', { name: 'Pay now' }).click();
// Verify success
await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible();
await expect(page.getByText(/Order #\d+/)).toBeVisible();
});
test('shows error for declined card', async ({ page }) => {
await page.goto('/checkout');
// Use test card that triggers decline
await page.getByLabel('Card number').fill('4000000000000002');
await page.getByLabel('Expiry').fill('12/25');
await page.getByLabel('CVC').fill('123');
await page.getByRole('button', { name: 'Pay now' }).click();
await expect(page.getByRole('alert')).toContainText('Card declined');
});
});javascript
import { test, expect } from '@playwright/test';
test.describe('结账流程', () => {
test.beforeEach(async ({ page }) => {
// 通过API预置测试数据
await page.request.post('/api/test/seed', {
data: { scenario: 'checkout-ready' }
});
});
test('使用信用卡完成购买', async ({ page }) => {
await page.goto('/cart');
// 使用可访问性选择器
await page.getByRole('button', { name: '前往结账' }).click();
// 填写支付表单
await page.getByLabel('卡号').fill('4242424242424242');
await page.getByLabel('有效期').fill('12/25');
await page.getByLabel('CVC').fill('123');
// 完成购买
await page.getByRole('button', { name: '立即支付' }).click();
// 验证成功状态
await expect(page.getByRole('heading', { name: '订单已确认' })).toBeVisible();
await expect(page.getByText(/Order #\d+/)).toBeVisible();
});
test('显示卡片被拒绝的错误提示', async ({ page }) => {
await page.goto('/checkout');
// 使用触发拒绝的测试卡号
await page.getByLabel('卡号').fill('4000000000000002');
await page.getByLabel('有效期').fill('12/25');
await page.getByLabel('CVC').fill('123');
await page.getByRole('button', { name: '立即支付' }).click();
await expect(page.getByRole('alert')).toContainText('卡片被拒绝');
});
});Flaky Test Detection & Prevention
不稳定测试检测与预防
Common Causes:
- Race conditions in async operations
- Time-dependent tests
- Shared state between tests
- Network variability
- Animation/transition timing
Fixes:
javascript
// ❌ Bad: Fixed timeout
await page.waitForTimeout(2000);
// ✅ Good: Wait for specific condition
await expect(page.getByText('Loaded')).toBeVisible();
// ❌ Bad: Checking exact time
expect(new Date()).toEqual(specificDate);
// ✅ Good: Mock time
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-15'));
// ❌ Bad: Depending on animation completion
await page.click('.button');
expect(await page.isVisible('.modal')).toBe(true);
// ✅ Good: Wait for animation
await page.click('.button');
await expect(page.locator('.modal')).toBeVisible();常见原因:
- 异步操作中的竞争条件
- 依赖时间的测试
- 测试间共享状态
- 网络波动
- 动画/过渡时序问题
修复方案:
javascript
// ❌ 错误:固定超时
await page.waitForTimeout(2000);
// ✅ 正确:等待特定条件
await expect(page.getByText('加载完成')).toBeVisible();
// ❌ 错误:检查精确时间
expect(new Date()).toEqual(specificDate);
// ✅ 正确:Mock时间
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-15'));
// ❌ 错误:依赖动画完成
await page.click('.button');
expect(await page.isVisible('.modal')).toBe(true);
// ✅ 正确:等待动画完成
await page.click('.button');
await expect(page.locator('.modal')).toBeVisible();Coverage Optimization
覆盖率优化
What to Measure
测量指标
| Metric | Target | Priority |
|---|---|---|
| Line coverage | 80%+ | Medium |
| Branch coverage | 75%+ | High |
| Function coverage | 90%+ | Medium |
| Critical path coverage | 100% | Critical |
| 指标 | 目标值 | 优先级 |
|---|---|---|
| 行覆盖率 | 80%+ | 中 |
| 分支覆盖率 | 75%+ | 高 |
| 函数覆盖率 | 90%+ | 中 |
| 关键路径覆盖率 | 100% | 极高 |
Coverage Configuration
覆盖率配置
javascript
// vitest.config.js
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'test/',
'**/*.d.ts',
'**/*.config.*',
'**/index.ts', // barrel files
],
thresholds: {
branches: 75,
functions: 80,
lines: 80,
statements: 80
}
}
}
});javascript
// vitest.config.js
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'test/',
'**/*.d.ts',
'**/*.config.*',
'**/index.ts', // 桶文件
],
thresholds: {
branches: 75,
functions: 80,
lines: 80,
statements: 80
}
}
}
});Finding Coverage Gaps
查找覆盖率缺口
bash
undefinedbash
undefinedGenerate detailed coverage report
生成详细覆盖率报告
npx vitest run --coverage
npx vitest run --coverage
Find untested files
查找未测试文件
npx vitest run --coverage --reporter=json | jq '.coverageMap | to_entries | map(select(.value.s | values | any(. == 0))) | .[].key'
undefinednpx vitest run --coverage --reporter=json | jq '.coverageMap | to_entries | map(select(.value.s | values | any(. == 0))) | .[].key'
undefinedCI/CD Integration
CI/CD集成
GitHub Actions
GitHub Actions
yaml
name: Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/yaml
name: Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/Test Parallelization
测试并行化
javascript
// vitest.config.js - parallel by default
export default defineConfig({
test: {
pool: 'threads',
poolOptions: {
threads: {
singleThread: false
}
}
}
});
// playwright.config.js
export default defineConfig({
workers: process.env.CI ? 2 : undefined,
fullyParallel: true
});javascript
// vitest.config.js - 默认启用并行
export default defineConfig({
test: {
pool: 'threads',
poolOptions: {
threads: {
singleThread: false
}
}
}
});
// playwright.config.js
export default defineConfig({
workers: process.env.CI ? 2 : undefined,
fullyParallel: true
});Anti-Patterns
反模式
Anti-Pattern: Testing Implementation Details
反模式:测试实现细节
What it looks like:
javascript
// ❌ Testing internal state
expect(component.state.isLoading).toBe(true);
// ❌ Testing private methods
expect(service._calculateHash()).toBe('abc123');Why wrong: Couples tests to implementation, breaks on refactors
Instead:
javascript
// ✅ Test observable behavior
expect(screen.getByRole('progressbar')).toBeInTheDocument();
// ✅ Test public interface
expect(service.getHash()).toBe('abc123');表现形式:
javascript
// ❌ 测试内部状态
expect(component.state.isLoading).toBe(true);
// ❌ 测试私有方法
expect(service._calculateHash()).toBe('abc123');问题所在: 测试与实现耦合,重构时易失效
正确做法:
javascript
// ✅ 测试可观察行为
expect(screen.getByRole('progressbar')).toBeInTheDocument();
// ✅ 测试公共接口
expect(service.getHash()).toBe('abc123');Anti-Pattern: Over-Mocking
反模式:过度Mock
What it looks like:
javascript
// ❌ Mocking everything
jest.mock('../utils/format');
jest.mock('../utils/validate');
jest.mock('../utils/transform');Why wrong: Tests pass even when real code is broken
Instead: Mock only at system boundaries (APIs, databases, external services)
表现形式:
javascript
// ❌ Mock所有依赖
jest.mock('../utils/format');
jest.mock('../utils/validate');
jest.mock('../utils/transform');问题所在: 真实代码出错时测试仍能通过
正确做法: 仅Mock系统边界(API、数据库、外部服务)
Anti-Pattern: Flaky Acceptance
反模式:容忍不稳定测试
What it looks like: "That test is just flaky, skip it"
Why wrong: Flaky tests indicate real problems (race conditions, timing issues)
Instead: Fix the flakiness or quarantine while fixing
表现形式: "这个测试就是不稳定,跳过它"
问题所在: 不稳定测试通常预示真实问题(竞争条件、时序问题)
正确做法: 修复不稳定问题,或在修复期间隔离测试
Anti-Pattern: Coverage Theater
反模式:覆盖率形式主义
What it looks like:
javascript
// ❌ Testing for coverage, not behavior
it('covers the function', () => {
myFunction();
// No assertions!
});Why wrong: 100% coverage with 0% confidence
Instead: Every test should assert meaningful behavior
表现形式:
javascript
// ❌ 为了覆盖率而测试,不验证行为
it('覆盖函数', () => {
myFunction();
// 无断言!
});问题所在: 100%覆盖率但0%可信度
正确做法: 每个测试都应验证有意义的行为
Quick Commands
快速命令
bash
undefinedbash
undefinedRun all tests
运行所有测试
npm test
npm test
Run with coverage
运行测试并生成覆盖率报告
npm test -- --coverage
npm test -- --coverage
Run specific file
运行指定文件的测试
npm test -- src/utils/format.test.ts
npm test -- src/utils/format.test.ts
Run in watch mode
以监听模式运行测试
npm test -- --watch
npm test -- --watch
Run E2E tests
运行E2E测试
npx playwright test
npx playwright test
Run E2E with UI
以UI模式运行E2E测试
npx playwright test --ui
npx playwright test --ui
Debug E2E test
调试E2E测试
npx playwright test --debug
npx playwright test --debug
Update snapshots
更新快照
npm test -- -u
undefinednpm test -- -u
undefinedReference Files
参考文件
- - Comprehensive test strategy framework
references/test-strategy.md - - Detailed framework comparison
references/framework-comparison.md - - Coverage optimization techniques
references/coverage-patterns.md - - CI/CD pipeline configurations
references/ci-integration.md
Covers: Test strategy | Unit testing | Integration testing | E2E testing | Coverage | CI/CD | Flaky test debugging
Use with: security-auditor (security tests) | performance-engineer (load tests) | code-reviewer (test quality)
- - 全面测试策略框架
references/test-strategy.md - - 详细框架对比
references/framework-comparison.md - - 覆盖率优化技巧
references/coverage-patterns.md - - CI/CD流水线配置
references/ci-integration.md
涵盖内容:测试策略 | 单元测试 | 集成测试 | E2E测试 | 测试覆盖率 | CI/CD | 不稳定测试调试
搭配使用:security-auditor(安全测试)| performance-engineer(负载测试)| code-reviewer(测试质量)