platform-apex-test-generate

platform-apex-test-generate

热门

生成并验证使用 TestDataFactory 模式、批量测试(251+ 条记录)、模拟策略、断言最佳实践以及规范的测试-修复循环的 Apex 测试类。在创建新的 Apex 测试类、提高测试覆盖率、调试和修复失败的 Apex 测试、运行测试执行和覆盖率分析,或为触发器、服务、控制器、批处理作业、队列和集成实现测试模式时使用此技能。触发条件:*Test.cls、*_Test.cls 文件、sf apex run test 工作流、覆盖率报告、测试-修复循环。不要为生产 Apex 代码(使用 platform-apex-generate)或 Jest/LWC 测试触发。

769Star
281Fork
更新于 2026/7/24
SKILL.md
readonly只读
name
platform-apex-test-generate
description

生成并验证使用 TestDataFactory 模式、批量测试(251+ 条记录)、模拟策略、断言最佳实践以及规范的测试-修复循环的 Apex 测试类。在创建新的 Apex 测试类、提高测试覆盖率、调试和修复失败的 Apex 测试、运行测试执行和覆盖率分析,或为触发器、服务、控制器、批处理作业、队列和集成实现测试模式时使用此技能。触发条件:*Test.cls、*_Test.cls 文件、sf apex run test 工作流、覆盖率报告、测试-修复循环。不要为生产 Apex 代码(使用 platform-apex-generate)或 Jest/LWC 测试触发。

生成 Apex 测试

生成可用于生产的 Apex 测试类,并通过覆盖率分析运行规范的测试-修复循环。

核心原则

  1. 每个方法一个行为 — 每个测试方法验证一个单一场景。将正向、负向和批量测试分开。切勿将相关但不同的输入(例如 null 和 empty)合并到一个方法中 — 应创建 _NullInput__EmptyInput_ 作为单独的测试方法
  2. 批量测试 — 使用 251+ 条记录进行测试,以跨越 200 条记录的触发器批处理边界。批处理 Apex 异常: 在测试上下文中,只有一个 execute() 调用运行,因此设置 batchSize >= testRecordCount。请参阅 references/async-testing.md
  3. 隔离测试数据 — 每个 @TestSetup 必须将记录创建委托给 TestDataFactory 类。如果不存在,请先创建一个。切勿在 @TestSetup 中内联构建记录列表。切勿依赖组织数据(SeeAllData=false)或硬编码 ID。有关重复规则处理,请参阅 references/test-data-factory.md
  4. 有意义的断言 — 使用从测试数据设置中计算出的精确预期值。当值确定时,切勿使用范围断言或近似计数。始终包含失败消息。请参阅 references/assertion-patterns.md
  5. 仅使用 AssertAssert.areEqualAssert.isTrueAssert.fail 等。切勿使用旧的 System.assertSystem.assertEqualsSystem.assertNotEquals
  6. 模拟外部边界 — 对出站调用使用 HttpCalloutMock,对 SOSL 使用 Test.setFixedSearchResults,对数据库隔离使用 DML 模拟类。通过构造函数注入设计可测试性。请参阅 references/mocking-patterns.md
  7. 测试负向路径 — 验证错误处理和异常场景,而不仅仅是快乐路径
  8. 使用 start/stop 包裹 — 将 Test.startTest()Test.stopTest() 配对,以重置调控器限制并强制异步执行

Test.startTest() / Test.stopTest()

始终将测试中的代码包裹在 Test.startTest() / Test.stopTest() 中:

  • 重置调控器限制,使测试仅衡量测试中的代码
  • 同步执行异步操作(队列、批处理、未来方法)
  • 立即触发计划作业

测试代码反模式

反模式 修复
循环内的 SOQL/DML 在循环前查询一次;使用 Map<Id, SObject> 进行查找
断言中的魔法数字 从设置常量中推导预期值
巨型测试类(>500 行) 按行为领域拆分为多个测试类
长测试方法(>30 行) 将 Given/When/Then 提取到辅助方法中
通用的 Exception 捕获 捕获特定的预期类型(例如 DmlException

工作流

步骤 1 — 收集上下文

在生成或修复测试之前,确定:

  • 被测的目标生产类
  • 现有的测试类、测试数据工厂和设置辅助类
  • 所需的测试范围(单个类、特定方法、套件或本地测试)
  • 覆盖率阈值(部署最低 75%,建议 90%+)
  • 针对组织运行测试时的组织别名

步骤 2 — 生成测试类

应用资产模板和参考文档中的结构、命名约定和模式。

强制 — 文件交付物: 对于每个测试类,创建两个文件:

  1. {ClassName}Test.cls — 测试类(使用 assets/test-class-template.cls 作为起点)
  2. {ClassName}Test.cls-meta.xml — 元数据文件:
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>66.0</apiVersion>
    <status>Active</status>
</ApexClass>

如果项目中不存在 TestDataFactory,请使用 assets/test-data-factory-template.cls 创建 TestDataFactory.cls + TestDataFactory.cls-meta.xml

@TestSetup 示例
@TestSetup
static void setupTestData() {
    List<Account> accounts = TestDataFactory.createAccounts(251, true);
}
测试方法结构

使用 Given/When/Then:

@isTest
static void shouldUpdateStatus_WhenValidInput() {
    // Given
    List<Account> accounts = [SELECT Id FROM Account];

    // When
    Test.startTest();
    MyService.processAccounts(accounts);
    Test.stopTest();

    // Then
    List<Account> updated = [SELECT Id, Status__c FROM Account];
    Assert.areEqual(251, updated.size(), '所有账户都应被处理');
}
负向测试 — 异常模式

使用 try/catch 配合 Assert.fail 验证预期异常:

@isTest
static void shouldThrowException_WhenInvalidInput() {
    // Given
    List<Account> emptyList = new List<Account>();

    // When/Then
    Test.startTest();
    try {
        MyService.processAccounts(emptyList);
        Assert.fail('预期抛出 MyCustomException');
    } catch (MyCustomException e) {
        Assert.isTrue(e.getMessage().contains('不能为空'),
            '异常消息应指示空输入');
    }
    Test.stopTest();
}
命名约定
  • should[ExpectedResult]_When[Scenario]shouldSendNotification_WhenOpportunityClosedWon
  • [SubjectOrAction]_[Scenario]_[ExpectedResult]AccountUpdate_ChangeName_Success

步骤 3 — 运行测试

调试时从窄范围开始;修复稳定后扩大范围。

# 单个测试类
sf apex run test --class-names MyServiceTest --result-format human --code-coverage --target-org <alias>

# 特定测试方法
sf apex run test --tests MyServiceTest.shouldUpdateStatus_WhenValidInput --result-format human --target-org <alias>

# 所有本地测试
sf apex run test --test-level RunLocalTests --result-format human --code-coverage --target-org <alias>

步骤 4 — 分析结果

重点关注:

  • 失败的方法 — 异常类型和堆栈跟踪
  • 未覆盖的行和弱覆盖区域
  • 失败是否表明测试数据错误、脆弱的断言或生产逻辑损坏

步骤 5 — 修复循环

当测试失败时,运行规范的修复循环(最多 3 次迭代 — 如果仍然失败,则停止并揭示根本原因):

  1. 阅读失败的测试类和被测类
  2. 从错误消息和堆栈跟踪中识别根本原因
  3. 应用修复 — 调整测试数据或断言以解决测试端问题;将生产代码问题委托给 platform-apex-generate 技能
  4. 在更广泛的回归测试之前重新运行聚焦的测试
  5. 重复直到所有测试通过、达到迭代限制或根本原因需要设计更改

步骤 6 — 验证覆盖率

级别 覆盖率 目的
生产部署 最低 75% Salesforce 要求
推荐 90%+ 最佳实践目标
关键路径 100% 业务关键代码

覆盖所有路径:正向、负向/异常、批量(251+ 条记录)、出站调用/异步。

按组件测试内容

组件 关键测试场景
触发器 批量插入/更新/删除、递归防护、字段更改检测
服务 有效/无效输入、批量操作、异常处理
控制器 页面加载、操作方法、视图状态
批处理 start/execute/finish、范围匹配(批处理大小 >= 记录数)、Database.Stateful 跟踪、错误处理、链式调用(单独方法 — finish() 调用 Database.executeBatch() 会抛出 UnexpectedException
队列 链式调用(测试中仅第一个作业运行)、批量处理、错误处理、Test.startTest() 之前的出站调用模拟
出站调用 成功响应、错误响应、超时
选择器 有效/null/空输入、批量(251+)、字段填充、排序顺序、通过 System.runAs 使用 WITH USER_MODE
计划 通过 execute(null) 直接执行、通过 CronTrigger 查询进行 CRON 注册
平台事件 Test.enableChangeDataCapture()Test.getEventBus().deliver()、验证订阅者副作用

输出期望

每个测试类的交付物:

  • {ClassName}Test.cls + {ClassName}Test.cls-meta.xml(匹配被测类的 API 版本;默认为 66.0
  • TestDataFactory.cls + TestDataFactory.cls-meta.xml(如果尚未存在)

参考文件

按需加载详细模式:

参考 使用时机
references/test-data-factory.md TestDataFactory 模式、字段覆盖、重复规则处理
references/assertion-patterns.md 断言最佳实践、反模式、常见陷阱
references/mocking-patterns.md HttpCalloutMock、DML 模拟、StubProvider、SOSL、电子邮件、平台事件
references/async-testing.md 批处理、队列、未来方法、计划作业测试