PIT Mutation Testing 導入指引

導入效應

效應 說明
測試有效性驗證 驗證你的測試是否真的在「測」,而不是 Coverage 100% 但沒價值
AI 低價值測試偵測 AI 產生的 assertNotNull(result) 100% Coverage 但沒意義,PIT 直接抓出
修改偵測 自動修改程式碼(如 >>=),如果測試仍通過,代表測試無效
Mutation Score 提供 Mutation Score 替代單純的 Line Coverage
CI 門檻 設定 Mutation Score ≥ 80% 才允許 Merge
找到弱點 自動找出哪些分支/條件沒有被測試覆蓋

1. Maven 導入

<plugin>
    <groupId>org.pitest</groupId>
    <artifactId>pitest-maven</artifactId>
    <version>1.17.3</version>
    <configuration>
        <targetClasses>com.example.*</targetClasses>
        <targetTests>com.example.*Test</targetTests>
        <mutationThreshold>80</mutationThreshold>
        <outputFormats>
            <outputFormat>XML</outputFormat>
            <outputFormat>HTML</outputFormat>
        </outputFormats>
        <timestampedReports>false</timestampedReports>
    </configuration>
</plugin>

2. Gradle 導入

plugins {
    id 'info.solidsoft.pitest' version '1.17.3'
}

pitest {
    targetClasses = ['com.example.*']
    targetTests = ['com.example.*Test']
    mutationThreshold = 80
    outputFormats = ['XML', 'HTML']
    timestampedReports = false
    threads = 4
    mutators = ['DEFAULTS', 'RETURN_VALS', 'NULL_VALS']
}

3. 執行 Mutation Testing

# Maven
mvn org.pitest:pitest-maven:mutationCoverage

# Gradle
./gradlew pitest

4. Mutation 操作類型

Mutator 修改方式 範例
RETURN_VALS 回傳值改為 null/0/false return resultreturn null
NULL_VALS 引入 null return listreturn null
NEGATE_CONDITIONALS 反轉條件 if (x > 0)if (x <= 0)
MATH 數學運算修改 x + 1x - 1
REMOVE_CONDITIONALS 移除條件判斷 if (condition) → 移除
VOID_METHOD_CALLS 移除方法呼叫 doSomething() → 移除

5. Mutation Score 解釋

=============
- Analyzed
=============

Generated 14 mutations
Killed 11 mutations
Survived 3 mutations

Mutation Score: 78.57%
指標 說明
Killed 測試成功偵測到修改(好的!)
Survived 修改後測試仍通過(測試太弱)
No Coverage 該行根本沒有被測試覆蓋

6. 常見 AI 產生的低價值測試

// ❌ AI 常產生
@Test
void testCreateOrder() {
    Order order = service.createOrder();
    assertNotNull(order);  // PIT 會修改 createOrder 回傳 null,測試仍過
}

// ✅ 有意義的測試
@Test
void testCreateOrder() {
    Order order = service.createOrder(new OrderRequest("item1", 2));
    assertNotNull(order);
    assertEquals("item1", order.getItemId());
    assertEquals(2, order.getQuantity());
    assertEquals(OrderStatus.PENDING, order.getStatus());
}

// ❌ AI 常產生的例外測試
@Test
void testDivide() {
    try {
        service.divide(10, 0);
        fail("Should throw");
    } catch (Exception e) {
        // 沒有驗證 exception 類型
    }
}

// ✅ 正確的例外測試
@Test
void testDivideByZero() {
    assertThrows(ArithmeticException.class, 
        () -> service.divide(10, 0));
}

7. CI 整合

7.1 Azure Pipeline

- stage: MutationTesting
  jobs:
    - job: PIT
      steps:
        - task: Maven@4
          inputs:
            goals: 'org.pitest:pitest-maven:mutationCoverage'
          displayName: 'PIT Mutation Testing'
        - task: PublishBuildArtifacts@1
          inputs:
            pathToPublish: 'target/pit-reports'
            artifactName: 'mutation-reports'
          condition: always()

7.2 GitHub Actions

- name: PIT Mutation Testing
  run: mvn org.pitest:pitest-maven:mutationCoverage

8. 進階設定

8.1 自訂 Mutators

<configuration>
    <mutators>
        <mutator>RETURN_VALS</mutator>
        <mutator>NEGATE_CONDITIONALS</mutator>
        <mutator>VOID_METHOD_CALLS</mutator>
        <mutator>DEFAULTS</mutator>
    </mutators>
</configuration>

8.2 排除特定類別

<configuration>
    <excludedClasses>
        <excludedClass>*Test</excludedClass>
        <excludedClass>*Config</excludedClass>
        <excludedClass>*Constants</excludedClass>
    </excludedClasses>
</configuration>

9. 與 Coverage 工具比較

特性 Line Coverage Mutation Score
衡量方式 程式碼是否被執行 測試是否偵測到變更
100% 是否等於好測試 ❌ 不一定 ✅ 大部分是
AI 產碼檢測 無法 ✅ 強力
執行速度 較慢
CI 適用性 需要較長時間

10. 參考資源