sf-test について
Salesforce 向けに、幅広いシナリオと明確なアサーションを含む Apex テストクラスの作成を支援するスキルです。テストデータの準備、非同期処理やプラットフォーム機能のテスト、実行とカバレッジ確認を案内します。
主な機能
- 01正常・異常・一括・権限・境界値テスト
- 02TestDataFactory によるテストデータ準備
- 03非同期処理・コールアウト・イベントのテストパターン
- 04Salesforce CLI によるテスト実行の案内
活用シーン
- 01Apex メソッドやコードパスのテスト作成
- 02200 件以上のレコードを使った一括処理の検証
- 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');
}
}
}
テストシナリオ(以下をすべて生成)
- 正常系テスト:有効なデータを使った正常なケース
- 異常系テスト:無効なデータ、null 入力、空のリスト
- 一括処理テスト:一括処理への対応を検証するため、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();
}
}
コールアウトのモックパターン
@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 件のレコードを使ってテストしてください
- private メソッド/変数を public にする代わりに、
@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 をアサートしてください
プラットフォームイベントと CDC のテスト
- プラットフォームイベント:公開後に
Test.getEventBus().deliver()を呼び出し、同期配信を強制してください - Change Data Capture:テストのセットアップで
Test.enableChangeDataCapture()を呼び出し、DML の後にTest.getEventBus().deliver()を呼び出してください
Stub API(依存性注入)
System.StubProvider インターフェースと Test.createStub() を使用すると、データベースにアクセスせずに依存関係をモックできます。
Test.loadData()
静的リソース内の 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.setMock())はTest.startTest()の前に登録する必要があります - テストではプラットフォームイベントの順序は保証されません
Test.startTest()/Test.stopTest()は各テストメソッドにつき 1 回しか呼び出せません- 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 対応