关于 sf-test
面向 Salesforce 的技能,用于生成包含多类场景和有效断言的 Apex 测试类,并指导测试数据设置、异步及平台功能测试、运行和覆盖率检查。
主要功能
- 01正向、负向、批量、权限和边界测试
- 02使用 TestDataFactory 设置测试数据
- 03异步、调用及平台事件测试模式
- 04Salesforce CLI 测试运行指引
适用场景
- 01为 Apex 方法和代码路径编写测试
- 02使用至少 200 条记录测试批量操作
- 03覆盖异步任务、调用或 REST 端点
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ clientell-ai/ salesforce-skills --skill sf-test安装后在 Claude Code 中输入/sf-test确认已加载。
附带文件references/test-patterns.md
详细介绍
Apex 测试类生成器
你是 Salesforce 测试类专家。生成全面的测试类,实现 85% 以上的代码覆盖率,并包含有意义的断言。
测试类结构
必需模式
@IsTest
private class MyClassTest {
@TestSetup
static void makeData() {
// Use TestFactory for all record creation
List<Account> accounts = TestDataFactory.createAccounts(200);
insert accounts;
List<Contact> contacts = TestDataFactory.createContacts(accounts);
insert contacts;
}
@IsTest
static void testMethodName_positiveScenario() {
// Arrange
List<Account> accounts = [SELECT Id, Name FROM Account WITH USER_MODE];
// Act
Test.startTest();
MyClass.myMethod(accounts);
Test.stopTest();
// Assert
List<Account> results = [SELECT Id, Status__c FROM Account WITH USER_MODE];
System.assertEquals(200, results.size(), 'All accounts should be processed');
for (Account acc : results) {
System.assertNotEquals(null, acc.Status__c, 'Status should be set');
}
}
}
测试场景(全部生成)
- 正向测试:使用有效数据测试正常路径
- 负向测试:测试无效数据、空值、空列表
- 批量测试:使用 200 条以上记录验证批量处理
- 权限测试:使用受限用户配置文件进行测试
- 边界测试:测试边界情况(0 条记录、1 条记录、最大记录数)
权限测试模式
@IsTest
static void testMethod_restrictedUser() {
User restrictedUser = TestDataFactory.createStandardUser();
insert restrictedUser;
System.runAs(restrictedUser) {
Test.startTest();
try {
MyClass.myMethod(testData);
System.assert(false, 'Should have thrown exception');
} catch (SecurityException e) {
System.assert(e.getMessage().contains('access'),
'Should throw security exception');
}
Test.stopTest();
}
}
Callout 模拟模式
@IsTest
private class MyCalloutClassTest {
private class MockHttpResponse implements HttpCalloutMock {
private Integer statusCode;
private String body;
MockHttpResponse(Integer statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(this.statusCode);
res.setBody(this.body);
return res;
}
}
@IsTest
static void testCallout_success() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, '{"status":"ok"}'));
Test.startTest();
String result = MyCalloutClass.makeCallout();
Test.stopTest();
System.assertEquals('ok', result, 'Should return success status');
}
@IsTest
static void testCallout_failure() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(500, '{"error":"fail"}'));
Test.startTest();
try {
MyCalloutClass.makeCallout();
System.assert(false, 'Should throw on 500');
} catch (CalloutException e) {
System.assert(true, 'Exception expected on server error');
}
Test.stopTest();
}
}
规则
- 绝不硬编码记录 ID——始终在
@TestSetup中查询或创建 - 始终使用
Test.startTest()和Test.stopTest()重置治理限制 - 始终使用
System.assertEquals/System.assertNotEquals并提供描述性消息 - 批量场景始终至少使用 200 条记录进行测试
- 对私有方法/变量使用
@TestVisible,而不是将其设为公共 - 如果不存在
TestDataFactory类,则创建一个 - 除非要测试特定平台功能,否则绝不使用
SeeAllData=true - 同时测试同步和异步路径(future、queueable、batch)
TestDataFactory 模式
@IsTest
public class TestDataFactory {
public static List<Account> createAccounts(Integer count) {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < count; i++) {
accounts.add(new Account(
Name = 'Test Account ' + i
));
}
return accounts;
}
public static User createStandardUser() {
Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
return new User(
FirstName = 'Test',
LastName = 'User',
Email = '[email protected]',
Username = 'testuser' + DateTime.now().getTime() + '@example.com',
Alias = 'tuser',
TimeZoneSidKey = 'America/Los_Angeles',
LocaleSidKey = 'en_US',
EmailEncodingKey = 'UTF-8',
ProfileId = p.Id,
LanguageLocaleKey = 'en_US'
);
}
}
异步测试模式
- @future:在
Test.stopTest()后运行——在 stopTest 后断言副作用 - Batch:在
Test.startTest()/Test.stopTest()之间调用Database.executeBatch() - Queueable:在 startTest/stopTest 之间调用
System.enqueueJob()——测试中的链式调用深度限制为 1 - Schedulable:在 startTest/stopTest 之间调用
System.schedule()——之后断言 CronTrigger
Platform Event 和 CDC 测试
- Platform Events:发布后调用
Test.getEventBus().deliver(),强制同步传递 - Change Data Capture:在测试设置中调用
Test.enableChangeDataCapture(),然后在 DML 后调用Test.getEventBus().deliver()
Stub API(依赖注入)
使用 System.StubProvider 接口和 Test.createStub() 模拟依赖项,无需访问数据库。
Test.loadData()
从 Static Resource 中的 CSV 加载批量测试数据:Test.loadData(Account.sObjectType, 'TestAccounts')
Mixed DML 变通方法
使用 System.runAs(),在同一测试中将设置对象的 DML(User、Profile)与非设置对象的 DML 分开。
特殊对象测试
- 对 Product2/PricebookEntry 测试使用
Test.getStandardPricebookId() - 对 @RestResource 端点测试使用
RestContext.request = new RestRequest()
注意事项
@TestSetup数据会在各测试方法间共享(不会隔离)——每个方法都会获得一份副本,且副本会重置SeeAllData=true会暴露生产数据——几乎不要使用- Future/Batch/Queueable 会在
Test.stopTest()之后执行,而不是在其执行期间 - 必须在
Test.startTest()之前注册 Callout 模拟(Test.setMock()) - 测试中不保证 Platform Event 的顺序
- 每个测试方法只能调用一次
Test.startTest()/Test.stopTest() - Batch Apex 的
finish()方法也会在Test.stopTest()之后运行 - Mixed DML 会抛出
MIXED_DML_OPERATION——使用System.runAs()解决
工作流
- 使用 Read/Glob 工具读取待测类
- 确认所有 public/global 方法和代码路径
- 检查是否存在 TestDataFactory;如果不存在则创建
- 生成包含所有场景类型的测试类
- 运行测试:
sf apex run test -n MyClassTest --synchronous --code-coverage - 报告覆盖率并修复所有失败
参考资料
评论
支持 Markdown