测试策略综合指南Skill test-strategy

这个技能提供全面的测试策略指导,包括测试金字塔设计、覆盖率目标、测试分类、不稳定测试诊断、测试基础设施架构和基于风险的优先级排序。适用于软件开发中的测试规划、设置测试基础设施、优化测试套件、诊断不稳定测试或设计跨领域测试架构。关键词:测试策略,测试金字塔,覆盖率,测试分类,CI/CD,风险优先,测试优化,API测试,数据管道测试,机器学习模型测试,基础设施测试。

测试 0 次安装 0 次浏览 更新于 3/24/2026

名称: 测试策略 描述: 全面测试策略指导,包括测试金字塔设计、覆盖率目标、测试分类、不稳定测试诊断、测试基础设施架构和基于风险的优先级排序。吸收了已淘汰高级质量保证工程师的专业知识。适用于规划测试方法、设置测试基础设施、优化测试套件、诊断不稳定测试或跨领域设计测试架构(API、数据管道、机器学习模型、基础设施)。触发关键词:测试策略、测试金字塔、测试计划、测试什么、如何测试、测试架构、测试基础设施、覆盖率目标、测试组织、CI/CD测试、测试优先级、测试方法、不稳定测试、测试优化、测试并行化、API测试策略、数据管道测试、机器学习模型测试、基础设施测试。

测试策略

概述

测试策略定义了项目测试的方法,平衡彻底性与效率。设计良好的策略确保关键功能被覆盖,同时避免过度测试琐碎代码。本技能涵盖测试金字塔、覆盖率指标、测试分类以及与CI/CD流水线的集成。

指令

1. 设计测试金字塔

以适当比例分层结构化测试:

         /\
        /  \        端到端测试 (5-10%)
       /----\       - 关键用户旅程
      /      \      - 跨系统集成
     /--------\     集成测试 (15-25%)
    /          \    - API合约
   /------------\   - 数据库交互
  /              \  - 服务边界
 /----------------\ 单元测试 (65-80%)
                    - 业务逻辑
                    - 纯函数
                    - 边界情况

推荐比例:

  • 单元测试:测试套件的65-80%
  • 集成测试:15-25%
  • 端到端测试:5-10%

2. 设置覆盖率目标

按组件类型的覆盖率目标:

组件类型 行覆盖率 分支覆盖率 注释
业务逻辑 90%+ 85%+ 关键路径完全覆盖
API处理器 80%+ 75%+ 所有端点测试
工具函数 95%+ 90%+ 纯函数易于测试
UI组件 70%+ 60%+ 关注行为而非标记
基础设施 60%+ 50%+ 首选集成测试

应避免的覆盖率反模式:

  • 为覆盖率而追求100%
  • 测试无逻辑的getter/setter
  • 测试框架或库代码
  • 编写不验证行为的测试

3. 决定测试什么与不测试什么

始终测试:

  • 业务逻辑和领域规则
  • 输入验证和错误处理
  • 安全敏感操作
  • 数据转换
  • 状态转换
  • 边界情况和边界条件
  • 来自错误修复的回归场景

考虑不测试:

  • 简单传递函数
  • 框架生成的代码
  • 第三方库内部
  • 琐碎的getter/setter
  • 配置常量
  • 日志语句(除非关键)

测试异味检测:

// 差:测试琐碎代码
test("getter返回值", () => {
  const user = new User("John");
  expect(user.getName()).toBe("John");
});

// 好:测试有意义的行为
test("用户不能将名称更改为空字符串", () => {
  const user = new User("John");
  expect(() => user.setName("")).toThrow(ValidationError);
});

4. 分类和组织测试

目录结构:

tests/
├── unit/
│   ├── services/
│   ├── models/
│   └── utils/
├── integration/
│   ├── api/
│   ├── database/
│   └── external-services/
├── e2e/
│   ├── flows/
│   └── pages/
├── fixtures/
│   ├── factories/
│   └── mocks/
└── helpers/
    ├── setup.ts
    └── assertions.ts

测试标记系统:

// Jest带标记示例
describe("[unit][fast] UserService", () => {});
describe("[integration][slow] DatabaseRepository", () => {});
describe("[e2e][critical] CheckoutFlow", () => {});

// 运行特定类别
// npm test -- --grep="\[unit\]"
// npm test -- --grep="\[critical\]"

命名约定:

[组件名称].[场景].[预期结果].test.ts

示例:
UserService.createUser.returnsNewUser.test.ts
PaymentProcessor.invalidCard.throwsPaymentError.test.ts

5. 与CI/CD集成

流水线阶段配置:

# .github/workflows/test.yml
名称: 测试流水线

触发: [push, pull_request]

作业:
  单元测试:
    运行于: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v4
      - 名称: 运行单元测试
        运行: npm test -- --grep="\[unit\]" --coverage
      - 名称: 上传覆盖率
        使用: codecov/codecov-action@v3

  集成测试:
    运行于: ubuntu-latest
    需要: 单元测试
    服务:
      postgres:
        镜像: postgres:15
        环境:
          POSTGRES_PASSWORD: test
    步骤:
      - 使用: actions/checkout@v4
      - 名称: 运行集成测试
        运行: npm test -- --grep="\[integration\]"

  端到端测试:
    运行于: ubuntu-latest
    需要: 集成测试
    步骤:
      - 使用: actions/checkout@v4
      - 名称: 运行端到端测试
        运行: npm run test:e2e

CI测试优化:

  • 首先运行单元测试(快速反馈)
  • 并行化测试套件
  • 缓存依赖项和构建工件
  • 对大型套件使用测试分片
  • 关键测试快速失败

6. 基于风险的测试优先级排序

优先级风险矩阵:

影响 ↓ / 可能性 →
中优先级 高优先级 关键
低优先级 中优先级 高优先级
跳过/手动 低优先级 中优先级

考虑的风险因素:

  • 业务影响: 收入、用户信任、法律合规性
  • 复杂性: 代码复杂性、集成点
  • 变更频率: 活跃开发区域
  • 历史错误: 有错误历史的组件
  • 依赖项: 关键外部服务

优先级测试类别:

  1. 关键 (P0): 每次提交运行

    • 认证/授权
    • 支付处理
    • 数据完整性
  2. 高 (P1): PR合并时运行

    • 核心业务工作流
    • API合约测试
  3. 中 (P2): 每晚运行

    • 边界情况
    • 性能测试
  4. 低 (P3): 每周运行

    • 向后兼容性
    • 弃用功能覆盖

7. 领域特定测试策略

API测试策略

测试层级:

  1. 合约测试 (P0)

    • 请求/响应模式验证
    • 所有端点的HTTP状态码
    • 错误响应格式
    • 认证/授权规则
  2. 业务逻辑测试 (P0)

    • 有效输入处理
    • 业务规则执行
    • 通过API调用的状态转换
  3. 集成测试 (P1)

    • 通过API的数据库操作
    • 外部服务集成
    • 事务回滚场景
  4. 性能测试 (P2)

    • 负载下的响应时间
    • 并发请求处理
    • 速率限制行为

API测试组织:

tests/api/
├── contracts/          # 模式验证测试
├── endpoints/          # 每端点行为测试
├── auth/               # 认证流程
├── integration/        # 跨服务场景
└── performance/        # 负载和压力测试

数据管道测试策略

测试重点领域:

  1. 数据质量测试 (P0)

    • 每阶段模式验证
    • 数据类型正确性
    • 空值/缺失值处理
    • 重复检测
  2. 转换测试 (P0)

    • 输入 → 输出正确性
    • 边界情况处理
    • 数据丢失检测
    • 聚合准确性
  3. 集成测试 (P1)

    • 源提取正确性
    • 汇加载验证
    • 幂等性检查
    • 故障恢复
  4. 性能测试 (P2)

    • 处理吞吐量
    • 大数据集内存使用
    • 分区处理

数据管道测试模式:

def test_user_data_transformation():
    # 安排:创建测试输入数据
    raw_input = create_test_dataset(
        rows=1000,
        include_nulls=True,
        include_duplicates=True
    )

    # 执行:运行转换
    result = transform_user_data(raw_input)

    # 断言:验证输出质量
    assert_no_nulls(result, required_fields=["user_id", "email"])
    assert_no_duplicates(result, key="user_id")
    assert_schema_matches(result, UserSchema)
    assert len(result) == expected_output_count(raw_input)

机器学习模型测试策略

测试层级:

  1. 数据验证测试 (P0)

    • 特征模式验证
    • 标签分布检查
    • 数据泄漏检测
    • 训练/测试分割正确性
  2. 模型行为测试 (P0)

    • 已知示例预测
    • 不变性测试(如大小写不敏感文本)
    • 方向性期望测试
    • 边界条件处理
  3. 模型质量测试 (P1)

    • 准确率/精确率/召回率阈值
    • 跨组公平性指标
    • 边界情况性能
    • 与基线回归检测
  4. 集成测试 (P1)

    • 模型加载和服务
    • 预测API合约
    • 特征工程流水线
    • 模型版本控制

机器学习测试示例:

def test_sentiment_model_invariance():
    """模型应大小写不敏感"""
    model = load_sentiment_model()

    test_cases = [
        ("This is GREAT!", "This is great!"),
        ("TERRIBLE service", "terrible service"),
    ]

    for text1, text2 in test_cases:
        pred1 = model.predict(text1)
        pred2 = model.predict(text2)
        assert pred1 == pred2, f"检测到大小写敏感性:{text1} vs {text2}"

基础设施测试策略

测试重点:

  1. 基础设施即代码测试 (P0)

    • 语法验证(terraform validate)
    • 安全策略检查
    • 资源命名约定
    • 成本估算验证
  2. 部署测试 (P1)

    • 部署后冒烟测试
    • 健康检查端点
    • 配置验证
    • 回滚程序
  3. 弹性测试 (P2)

    • 服务重启处理
    • 网络分区恢复
    • 资源耗尽场景
    • 混沌工程测试
  4. 可观察性测试 (P1)

    • 指标收集验证
    • 日志聚合正确性
    • 警报规则验证
    • 仪表板功能

基础设施测试模式:

# terraform测试示例
run "verify_security_group_rules" {
  command = plan

  assert {
    condition     = length([for rule in aws_security_group.main.ingress : rule if rule.cidr_blocks[0] == "0.0.0.0/0"]) == 0
    error_message = "安全组不应允许来自0.0.0.0/0的入站流量"
  }
}

8. 不稳定测试诊断与预防

不稳定性常见原因:

原因 症状 解决方案
竞态条件 时间上间歇性失败 添加适当同步
异步操作 因“元素未找到”失败 使用显式等待,而非睡眠
共享状态 与其他测试一起运行时失败 隔离测试数据,重置状态
外部依赖 服务不可用时失败 模拟外部调用,使用测试替身
时间依赖逻辑 在特定时间/日期失败 注入时间,使用假时钟
资源清理 特定测试顺序后失败 确保清理始终运行
非确定性数据 随机数据变化失败 使用固定种子,确定性生成器
环境差异 CI失败但本地通过 容器化测试环境
超时不足 负载/慢机器下失败 使超时可配置
并行执行竞态 仅并行化时失败 使用每个测试的唯一标识符

不稳定测试诊断工作流:

1. 本地复现
   ├─ 运行测试100次:`for i in {1..100}; do npm test -- TestName || break; done`
   ├─ 用不同种子运行:`npm test -- --seed=$RANDOM`
   └─ 并行运行:`npm test -- --maxWorkers=4`

2. 识别模式
   ├─ 总是在同一点失败?→ 逻辑错误,非不稳定
   ├─ 负载下失败?→ 时间/资源问题
   ├─ 与其他测试一起失败?→ 共享状态污染
   └─ 特定数据失败?→ 数据依赖错误

3. 工具化测试
   ├─ 添加详细日志
   ├─ 捕获时间信息
   ├─ 记录测试环境状态
   └─ 保存失败工件(截图、日志)

4. 修复根本原因
   ├─ 消除竞态条件
   ├─ 添加适当同步
   ├─ 隔离测试状态
   └─ 模拟外部依赖

5. 验证修复
   ├─ 运行修复测试1000次
   ├─ 在CI中运行10次
   └─ 监控一周

不稳定测试预防清单:

  • [ ] 测试使用确定性测试数据(固定种子,无random())
  • [ ] 异步操作使用显式等待(非setTimeout/sleep)
  • [ ] 测试创建唯一资源(名称/ID中的UUID)
  • [ ] 清理始终运行(try/finally,afterEach钩子)
  • [ ] 无硬编码时间假设(sleep(100)是代码异味)
  • [ ] 外部服务被模拟或使用测试替身
  • [ ] 时间依赖逻辑使用注入/假时钟
  • [ ] 测试不依赖执行顺序
  • [ ] 共享状态在测试间重置
  • [ ] 测试环境可重现(容器化)

示例:修复不稳定测试

// 不稳定:异步操作竞态条件
test("用户配置文件加载", async () => {
  renderUserProfile(userId);
  // 竞态:配置文件可能尚未加载
  expect(screen.getByText("John Doe")).toBeInTheDocument();
});

// 修复:适当的异步处理
test("用户配置文件加载", async () => {
  renderUserProfile(userId);
  // 等待异步操作完成
  const userName = await screen.findByText("John Doe");
  expect(userName).toBeInTheDocument();
});

// 不稳定:共享状态污染
test("以默认角色创建用户", () => {
  const user = createUser({ name: "Alice" });
  expect(user.role).toBe("user"); // 如果先前测试修改了默认值则失败
});

// 修复:隔离状态
test("以默认角色创建用户", () => {
  resetDefaultRole(); // 确保干净状态
  const user = createUser({ name: "Alice" });
  expect(user.role).toBe("user");
});

// 不稳定:时间依赖逻辑
test("1小时后会话过期", () => {
  const session = createSession();
  // 不稳定:依赖当前时间
  expect(session.expiresAt).toBe(Date.now() + 3600000);
});

// 修复:注入时间依赖
test("1小时后会话过期", () => {
  const mockClock = installFakeClock();
  mockClock.setTime(new Date("2024-01-01T12:00:00Z"));

  const session = createSession();
  expect(session.expiresAt).toBe(new Date("2024-01-01T13:00:00Z").getTime());

  mockClock.uninstall();
});

9. 测试基础设施架构

测试环境管理:

# docker-compose.test.yml
版本: "3.8"
服务:
  测试数据库:
    镜像: postgres:15
    环境:
      POSTGRES_DB: test_db
      POSTGRES_USER: test_user
      POSTGRES_PASSWORD: test_pass
    端口:
      - "5433:5432"
    tmpfs:
      - /var/lib/postgresql/data # 内存中以提高速度

  测试Redis:
    镜像: redis:7-alpine
    端口:
      - "6380:6379"

  测试应用:
    构建: .
    环境:
      DATABASE_URL: postgres://test_user:test_pass@测试数据库:5432/test_db
      REDIS_URL: redis://测试Redis:6379
    依赖于:
      - 测试数据库
      - 测试Redis

测试数据管理:

// 测试数据的工厂模式
class UserFactory {
  private sequence = 0;

  create(overrides?: Partial<User>): User {
    return {
      id: overrides?.id ?? `user-${this.sequence++}`,
      email: overrides?.email ?? `user${this.sequence}@test.com`,
      name: overrides?.name ?? `Test User ${this.sequence}`,
      role: overrides?.role ?? "user",
      createdAt: overrides?.createdAt ?? new Date(),
    };
  }

  createBatch(count: number, overrides?: Partial<User>): User[] {
    return Array.from({ length: count }, () => this.create(overrides));
  }
}

// 使用确保每个测试有唯一数据
test("用户搜索工作", () => {
  const factory = new UserFactory();
  const users = factory.createBatch(10);
  // 每个测试获取唯一用户,无冲突
});

测试并行化策略:

策略 何时使用 配置
文件级并行 不同文件中测试独立 Jest: --maxWorkers=4
每工作器数据库 测试需要数据库隔离 Postgres: 为每个工作器创建模式
测试分片 CI中有多个机器 按分片拆分测试:--shard=1/4
测试优先级排序 希望快速反馈 首先运行快速测试,慢测试并行运行
智能测试选择 仅运行受影响测试 使用依赖图选择更改的测试

示例:并行测试配置

// 带有并行优化的jest.config.js
module.exports = {
  maxWorkers: process.env.CI ? "50%" : "75%", // CI中保守
  testTimeout: 30000, // CI中更长的超时

  // 首先运行快速测试
  testSequencer: "./custom-sequencer.js",

  // 每个工作器的数据库隔离
  globalSetup: "./tests/setup/create-test-dbs.js",
  globalTeardown: "./tests/setup/drop-test-dbs.js",

  // CI中分片测试
  shard: process.env.CI_NODE_INDEX
    ? `${process.env.CI_NODE_INDEX}/${process.env.CI_NODE_TOTAL}`
    : undefined,
};

测试优化技术:

  1. 减少测试启动时间

    • 缓存编译代码
    • 懒加载测试依赖项
    • 对单元测试使用内存数据库
  2. 优化测试执行

    • 批量数据库操作
    • 重用昂贵固定装置(连接、容器)
    • 为聚焦测试跳过不必要的设置
  3. 安全并行化

    • 每个测试的唯一标识符(UUID)
    • 每个工作器的独立数据库模式
    • 避免共享文件系统访问
  4. 智能测试选择

    • 开发期间仅运行受影响测试
    • 使用覆盖率映射确定受影响测试
    • 缓存未更改代码的测试结果
# 仅运行受更改影响的测试
npm test -- --changedSince=origin/main

# 运行特定模块及其依赖项的测试
npm test -- --selectProjects=user-service --testPathPattern=user

# 带有智能重新运行的监视模式
npm test -- --watch --changedSince=HEAD

最佳实践

  1. 测试行为,而非实现

    • 测试应验证结果,而非内部机制
    • 如果行为未变,重构不应破坏测试
  2. 保持测试独立

    • 测试间无共享可变状态
    • 每个测试设置自己的上下文
    • 测试可以任何顺序运行
  3. 适当使用测试替身

    • 用于提供测试数据的桩
    • 用于验证交互的模拟
    • 用于复杂依赖的假对象
    • 可行时使用真实实现
  4. 维护测试质量

    • 对测试应用相同的代码质量标准
    • 重构测试代码以提高可读性
    • 及时移除过时测试
  5. 快速反馈循环

    • 优化快速本地测试运行
    • 开发期间使用监视模式
    • CI中优先快速测试
  6. 记录测试意图

    • 清晰的测试名称描述行为
    • 为非明显设置添加注释
    • 链接测试到需求/工单

示例

示例:功能测试策略文档

# 功能:用户注册

## 风险评估

- 业务影响:高(用户获取)
- 复杂性:中(邮件验证、密码规则)
- 变更频率:低(稳定功能)

## 测试覆盖率计划

### 单元测试 (P0)

- [ ] 邮件格式验证
- [ ] 密码强度要求
- [ ] 用户名唯一性检查逻辑
- [ ] 配置文件数据清理

### 集成测试 (P1)

- [ ] 数据库用户创建
- [ ] 邮件服务集成
- [ ] 重复邮件处理

### 端到端测试 (P0)

- [ ] 顺利路径:完成注册流程
- [ ] 错误路径:重复邮件显示错误

## 覆盖率目标

- 行覆盖率:85%
- 分支覆盖率:80%
- 关键路径:100%

示例:测试组织配置

// jest.config.js
module.exports = {
  projects: [
    {
      displayName: "单元",
      testMatch: ["<rootDir>/tests/unit/**/*.test.ts"],
      setupFilesAfterEnv: ["<rootDir>/tests/helpers/unit-setup.ts"],
    },
    {
      displayName: "集成",
      testMatch: ["<rootDir>/tests/integration/**/*.test.ts"],
      setupFilesAfterEnv: ["<rootDir>/tests/helpers/integration-setup.ts"],
      globalSetup: "<rootDir>/tests/helpers/db-setup.ts",
      globalTeardown: "<rootDir>/tests/helpers/db-teardown.ts",
    },
  ],
  coverageThreshold: {
    global: {
      branches: 75,
      functions: 80,
      lines: 80,
      statements: 80,
    },
    "./src/services/": {
      branches: 90,
      lines: 90,
    },
  },
};

示例:基于风险的测试选择脚本

// scripts/select-tests.ts
interface TestFile {
  path: string;
  priority: "P0" | "P1" | "P2" | "P3";
  tags: string[];
}

function selectTestsForPipeline(
  context: "commit" | "pr" | "nightly" | "weekly",
): TestFile[] {
  const allTests = getTestManifest();

  const priorityMap = {
    commit: ["P0"],
    pr: ["P0", "P1"],
    nightly: ["P0", "P1", "P2"],
    weekly: ["P0", "P1", "P2", "P3"],
  };

  return allTests.filter((test) =>
    priorityMap[context].includes(test.priority),
  );
}