
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('cannot be empty'),
'例外訊息應指出輸入為空');
}
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 <別名>
# 特定測試方法
sf apex run test --tests MyServiceTest.shouldUpdateStatus_WhenValidInput --result-format human --target-org <別名>
# 所有本地測試
sf apex run test --test-level RunLocalTests --result-format human --code-coverage --target-org <別名>
步驟 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 | 批次、佇列作業、未來方法、排程作業測試 |





