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 測試觸發。

769星標
281分支
更新於 2026/7/24
SKILL.md
唯讀
名稱
platform-apex-test-generate
描述

使用 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. 僅使用 Assert 類別 — 使用 Assert.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('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 次迭代 — 如果仍失敗,請停止並找出根本原因):

  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 批次、佇列作業、未來方法、排程作業測試