关于 test-automation-expert
为测试策略、自动化测试实现、覆盖率改进和不稳定测试排查提供指导,涵盖单元、集成及端到端测试,并包含 JavaScript/TypeScript 与 Python 示例。
主要功能
- 01制定测试策略
- 02编写单元、集成和端到端测试
- 03选择测试框架
- 04提升覆盖率并查找缺口
- 05排查不稳定测试
- 06配置 CI/CD 测试流水线
适用场景
- 01为新项目搭建测试
- 02验证关键用户流程
- 03诊断不稳定的自动化测试
- 04将测试加入 CI/CD
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill test-automation-expert安装后在 Claude Code 中输入/test-automation-expert确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.md.claude-plugin/plugin.jsonreferences/ci-integration.mdcoverage-patterns.mdframework-comparison.mdtest-strategy.md详细介绍
测试自动化专家
提供从单元测试到端到端(E2E)的全面测试指导。设计测试策略、实现自动化,并优化覆盖率,以实现可持续的质量保障。
适用场景
适用于:
- 为新项目设计测试策略
- 搭建测试框架(Jest、Vitest、Playwright、Cypress、pytest)
- 编写有效的单元测试、集成测试和 E2E 测试
- 优化测试覆盖率并消除盲区
- 调试不稳定的测试
- 配置 CI/CD 测试流水线
- 获取测试驱动开发(TDD)指导
- 制定模拟策略和测试固定装置
不适用于:
- 编写手动 QA 测试用例——本指南侧重自动化
- 负载/性能测试——请使用 performance-engineer skill
- 安全测试——请使用 security-auditor skill
- 仅进行 API 契约测试——API 设计请使用 backend-architect
测试金字塔理念
/\
/ \ 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
分布指南
| 测试类型 | 百分比 | 执行时间 | 目的 |
|---|---|---|---|
| 单元测试 | 70% | < 100ms each | 逻辑验证 |
| 集成测试 | 20% | < 1s each | 组件契约 |
| E2E | 10% | < 30s each | 关键路径 |
框架选择
JavaScript/TypeScript
| 框架 | 最适合 | 速度 | 配置复杂度 |
|---|---|---|---|
| Vitest | Vite 项目、现代 ESM | 最快 | 低 |
| Jest | React、成熟项目 | 快 | 中 |
| Playwright | E2E、跨浏览器 | N/A | 低 |
| Cypress | E2E、组件测试 | N/A | 中 |
Python
| 框架 | 最适合 | 速度 | 功能 |
|---|---|---|---|
| pytest | 所有场景 | 快 | 固定装置、插件 |
| unittest | 标准库 | 中 | 内置 |
| hypothesis | 基于属性的测试 | 不定 | 生成式 |
决策树:框架选择
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)
单元测试模式
良好的单元测试结构
describe('UserService', () => {
describe('validateEmail', () => {
// Arrange-Act-Assert pattern
it('should accept valid email formats', () => {
// Arrange
const validEmails = ['[email protected]', '[email protected]'];
// 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);
});
});
});
模拟策略
// ✅ 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 internals
测试隔离检查清单
- 每项测试都能独立运行
- 测试之间没有共享的可变状态
- 测试之间重置数据库/API 状态
- 不依赖测试顺序
- 可安全并行执行
集成测试模式
API 集成测试
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: '[email protected]' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
name: 'Test',
email: '[email protected]'
});
// 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: '[email protected]' });
await request(app)
.post('/api/users')
.send({ name: 'New', email: '[email protected]' })
.expect(409);
});
});
组件集成(React)
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();
});
});
E2E 测试模式
Playwright 最佳实践
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');
});
});
不稳定测试的检测与预防
常见原因:
- 异步操作中的竞态条件
- 依赖时间的测试
- 测试之间共享状态
- 网络波动
- 动画/过渡时间
修复方法:
// ❌ 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();
覆盖率优化
衡量指标
| 指标 | 目标 | 优先级 |
|---|---|---|
| 行覆盖率 | 80%+ | 中 |
| 分支覆盖率 | 75%+ | 高 |
| 函数覆盖率 | 90%+ | 中 |
| 关键路径覆盖率 | 100% | 关键 |
覆盖率配置
// 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
}
}
}
});
查找覆盖率盲区
# Generate detailed coverage report
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'
CI/CD 集成
GitHub Actions
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/
测试并行化
// 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
});
反模式
反模式:测试实现细节
表现形式:
// ❌ Testing internal state
expect(component.state.isLoading).toBe(true);
// ❌ Testing private methods
expect(service._calculateHash()).toBe('abc123');
问题所在: 测试与实现耦合,重构时容易失败
替代方案:
// ✅ Test observable behavior
expect(screen.getByRole('progressbar')).toBeInTheDocument();
// ✅ Test public interface
expect(service.getHash()).toBe('abc123');
反模式:过度模拟
表现形式:
// ❌ Mocking everything
jest.mock('../utils/format');
jest.mock('../utils/validate');
jest.mock('../utils/transform');
问题所在: 即使真实代码已损坏,测试仍会通过
替代方案: 仅在系统边界(API、数据库、外部服务)进行模拟
反模式:对不稳定测试听之任之
表现形式:“那个测试不稳定,跳过吧”
问题所在: 不稳定的测试表明存在真实问题(竞态条件、时序问题)
替代方案: 修复不稳定因素,或在修复期间将测试隔离
反模式:覆盖率作秀
表现形式:
// ❌ Testing for coverage, not behavior
it('covers the function', () => {
myFunction();
// No assertions!
});
问题所在: 100% 覆盖率,但毫无信心
替代方案: 每项测试都应断言有意义的行为
快速命令
# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run specific file
npm test -- src/utils/format.test.ts
# Run in watch mode
npm test -- --watch
# Run E2E tests
npx playwright test
# Run E2E with UI
npx playwright test --ui
# Debug E2E test
npx playwright test --debug
# Update snapshots
npm test -- -u
参考文件
references/test-strategy.md- 全面的测试策略框架references/framework-comparison.md- 详细的框架对比references/coverage-patterns.md- 覆盖率优化技巧references/ci-integration.md- CI/CD 流水线配置
涵盖内容:测试策略 | 单元测试 | 集成测试 | 端到端测试 | 覆盖率 | CI/CD | 不稳定测试调试
搭配使用:security-auditor(安全测试)| performance-engineer(负载测试)| code-reviewer(测试质量)
评论
支持 Markdown