test-automation-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Test 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 TypePercentageExecution TimePurpose
Unit70%< 100ms eachLogic validation
Integration20%< 1s eachComponent contracts
E2E10%< 30s eachCritical paths
测试类型占比执行时间目的
单元测试70%< 100ms 每个逻辑验证
集成测试20%< 1s 每个组件契约验证
E2E测试10%< 30s 每个关键流程验证

Framework Selection

框架选择

JavaScript/TypeScript

JavaScript/TypeScript

FrameworkBest ForSpeedConfig Complexity
VitestVite projects, modern ESMFastestLow
JestReact, established projectsFastMedium
PlaywrightE2E, cross-browserN/ALow
CypressE2E, component testingN/AMedium
框架最佳适用场景速度配置复杂度
VitestVite项目、现代ESM环境最快
JestReact项目、成熟项目
PlaywrightE2E测试、跨浏览器测试N/A
CypressE2E测试、组件测试N/A

Python

Python

FrameworkBest ForSpeedFeatures
pytestEverythingFastFixtures, plugins
unittestStandard libraryMediumBuilt-in
hypothesisProperty-basedVariesGenerative
框架最佳适用场景速度功能特性
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 internals
javascript
// ✅ 正确:在边界处进行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:
  1. Race conditions in async operations
  2. Time-dependent tests
  3. Shared state between tests
  4. Network variability
  5. 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();
常见原因:
  1. 异步操作中的竞争条件
  2. 依赖时间的测试
  3. 测试间共享状态
  4. 网络波动
  5. 动画/过渡时序问题
修复方案:
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

测量指标

MetricTargetPriority
Line coverage80%+Medium
Branch coverage75%+High
Function coverage90%+Medium
Critical path coverage100%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
undefined
bash
undefined

Generate 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'
undefined
npx vitest run --coverage --reporter=json | jq '.coverageMap | to_entries | map(select(.value.s | values | any(. == 0))) | .[].key'
undefined

CI/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
undefined
bash
undefined

Run 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
undefined
npm test -- -u
undefined

Reference Files

参考文件

  • references/test-strategy.md
    - Comprehensive test strategy framework
  • references/framework-comparison.md
    - Detailed framework comparison
  • references/coverage-patterns.md
    - Coverage optimization techniques
  • references/ci-integration.md
    - CI/CD pipeline configurations

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
    - 覆盖率优化技巧
  • references/ci-integration.md
    - CI/CD流水线配置

涵盖内容:测试策略 | 单元测试 | 集成测试 | E2E测试 | 测试覆盖率 | CI/CD | 不稳定测试调试
搭配使用:security-auditor(安全测试)| performance-engineer(负载测试)| code-reviewer(测试质量)