
platform-apex-test-generate
热门生成并验证使用 TestDataFactory 模式、批量测试(251+ 条记录)、模拟策略、断言最佳实践以及规范的测试-修复循环的 Apex 测试类。在创建新的 Apex 测试类、提高测试覆盖率、调试和修复失败的 Apex 测试、运行测试执行和覆盖率分析,或为触发器、服务、控制器、批处理作业、队列和集成实现测试模式时使用此技能。触发条件:*Test.cls、*_Test.cls 文件、sf apex run test 工作流、覆盖率报告、测试-修复循环。不要为生产 Apex 代码(使用 platform-apex-generate)或 Jest/LWC 测试触发。
生成并验证使用 TestDataFactory 模式、批量测试(251+ 条记录)、模拟策略、断言最佳实践以及规范的测试-修复循环的 Apex 测试类。在创建新的 Apex 测试类、提高测试覆盖率、调试和修复失败的 Apex 测试、运行测试执行和覆盖率分析,或为触发器、服务、控制器、批处理作业、队列和集成实现测试模式时使用此技能。触发条件:*Test.cls、*_Test.cls 文件、sf apex run test 工作流、覆盖率报告、测试-修复循环。不要为生产 Apex 代码(使用 platform-apex-generate)或 Jest/LWC 测试触发。
生成 Apex 测试
生成可用于生产的 Apex 测试类,并通过覆盖率分析运行规范的测试-修复循环。
核心原则
- 每个方法一个行为 — 每个测试方法验证一个单一场景。将正向、负向和批量测试分开。切勿将相关但不同的输入(例如 null 和 empty)合并到一个方法中 — 应创建
_NullInput_和_EmptyInput_作为单独的测试方法 - 批量测试 — 使用 251+ 条记录进行测试,以跨越 200 条记录的触发器批处理边界。批处理 Apex 异常: 在测试上下文中,只有一个
execute()调用运行,因此设置batchSize >= testRecordCount。请参阅 references/async-testing.md - 隔离测试数据 — 每个
@TestSetup必须将记录创建委托给TestDataFactory类。如果不存在,请先创建一个。切勿在@TestSetup中内联构建记录列表。切勿依赖组织数据(SeeAllData=false)或硬编码 ID。有关重复规则处理,请参阅 references/test-data-factory.md - 有意义的断言 — 使用从测试数据设置中计算出的精确预期值。当值确定时,切勿使用范围断言或近似计数。始终包含失败消息。请参阅 references/assertion-patterns.md
- 仅使用
Assert类 —Assert.areEqual、Assert.isTrue、Assert.fail等。切勿使用旧的System.assert、System.assertEquals或System.assertNotEquals - 模拟外部边界 — 对出站调用使用
HttpCalloutMock,对 SOSL 使用Test.setFixedSearchResults,对数据库隔离使用 DML 模拟类。通过构造函数注入设计可测试性。请参阅 references/mocking-patterns.md - 测试负向路径 — 验证错误处理和异常场景,而不仅仅是快乐路径
- 使用 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 — 生成测试类
应用资产模板和参考文档中的结构、命名约定和模式。
强制 — 文件交付物: 对于每个测试类,创建两个文件:
{ClassName}Test.cls— 测试类(使用 assets/test-class-template.cls 作为起点){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 次迭代 — 如果仍然失败,则停止并揭示根本原因):
- 阅读失败的测试类和被测类
- 从错误消息和堆栈跟踪中识别根本原因
- 应用修复 — 调整测试数据或断言以解决测试端问题;将生产代码问题委托给
platform-apex-generate技能 - 在更广泛的回归测试之前重新运行聚焦的测试
- 重复直到所有测试通过、达到迭代限制或根本原因需要设计更改
步骤 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 | 批处理、队列、未来方法、计划作业测试 |





