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 result → return null |
| NULL_VALS |
引入 null |
return list → return null |
| NEGATE_CONDITIONALS |
反轉條件 |
if (x > 0) → if (x <= 0) |
| MATH |
數學運算修改 |
x + 1 → x - 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. 參考資源