CR #201 » CSP-005_單元測試與品質規範.md
CSP-005 單元測試與品質規範(Unit Test & Quality)
| 項目 | 內容 |
|---|---|
| 文件編號 | CSP-005 |
| 版本 | 1.0 |
| 狀態 | 已核定 |
| 撰寫者 | SA / QA Lead |
| 審查者 | PMO / TestBOT |
| 對應需求 | NFR-QUAL 品質群組;BRD-004 POC 驗收情境 |
1. 目的
定義單元測試的撰寫標準、覆蓋門檻、品質閘與禁止事項,確保交付程式碼具備可驗證的正確性。
本規範與 TRP-001(測試計畫)互補:TRP-001 管「驗收測試劇本」,本規範管「開發期單元測試」。
2. 測試金字塔
flowchart TD
U[Unit 單元測試 ~70%] --> I[Integration 整合 ~20%]
I --> E[End-to-End / POC 驗收 ~10%]
style U fill:#EEF3F8,stroke:#0B2E59
style E fill:#FFF6E0,stroke:#C8A45C
-
單元測試:不啟動 Spring 容器、不連 DB;以純邏輯測
domain/與application/handler。 -
整合測試:以 Testcontainers 起 PostgreSQL / Kafka,驗證
infrastructure/真實接線。 - E2E / POC:對應 BRD-004 的 17 項情境,經 api-gateway 端到端跑。
3. 單元測試撰寫約定
-
框架:JUnit 5(
@Test)、AssertJ(斷言)、Mockito(僅用於隔離外部依賴,不得 mock 被測邏輯本身)。 -
命名:
<受測方法>_<情境>_<預期>。例:approve_whenLimitSufficient_returnsApproved()。 - 結構:遵守 Given-When-Then;每個測試只驗證一個行為。
-
資料:測試內的
given資料合法(真實格式的卡號、金額),不得用null或無意義假值掩飾。 - 邊界:每個業務規則至少 3 例(正常值、下界、上界/例外)。
4. 覆蓋率與品質閘(CI 強制)
| 指標 | 門檻 | 工具 |
|---|---|---|
| 行覆蓋率(line) | ≥ 80% | JaCoCo |
| 分支覆蓋率(branch) | ≥ 70% | JaCoCo |
| Mutation 存活率 | ≤ 25%(即殺死率 ≥ 75%) | PITest |
| 靜態分析 | 0 error | Checkstyle / SpotBugs / PMD |
| 禁止模式掃描 | 0 命中 | scan_scaffold / scan_mockdata |
門檻為合併前紅線:任一未達標,CI 失敗、PR 不得合併。
4.1 量測方式(2026-10-02 補;#A-160)
| 項目 | 內容 |
|---|---|
| 工具接線 |
backend/build.gradle.kts 之 subprojects {} 內 apply(plugin = "jacoco")(由 tools/gen_service_skeleton.py 之 T_ROOT_BUILD 產生——改動須兩邊都動) |
| 產出 | 每個模組 build/reports/jacoco/test/html/(人看)+ build/reports/jacoco/test/jacocoTestReport.xml(供 tools/verify_nfr.py 解析) |
| 執行 | cd backend && ./gradlew test jacocoTestReport |
| 門檻任務 |
jacocoTestCoverageVerification 已定義(行 0.80/分支 0.70);⚠️ 尚未接入 check——待基線補足後開啟(見 TRP-002 §7.2) |
| Mutation | PITest(≥75%)尚未接入——PIT 執行成本高,另立任務 |
4.1.1 ★ 排除規則(刻意從嚴)
僅排除「機器產生」與「純啟動類」兩類,其餘一律計入:
| 排除樣式 | 理由 |
|---|---|
**/ErrorCodes.class |
由 tools/gen_error_codes.py 產生(依 SDD-006 §3.4)——非人手撰寫 |
**/*Application.class |
Spring Boot 啟動類(無業務邏輯) |
⚠️ 刻意不排除 DTO/View/Request/Config——業界慣例常排除之,但那會虛胖覆蓋率。
本專案之record雖多為資料載體,仍可能有驗證與正規化邏輯(例:SpecialLimit#coversMcc)。
寧可數字難看,不要數字騙人(GOV-007反假綠)。
如日後確有排除需求,須逐類具名並於本節載明理由,不得以「**/dto/**」通吃。
⚠️ 未接入
check之門檻=目前不會擋 PR——此為已知之過渡狀態,
目的在「先量到真實基線」再設門檻;TRP-002 §7.2 #1已列此缺口。
4.2 ★★ 實測基線(2026-10-02,#A-160)
| 指標 | 實測 | 門檻 | 判定 |
|---|---|---|---|
| 行覆蓋率 | 53.7%(8,034/14,970) | ≥ 80% | ❌ 未達 |
| 分支覆蓋率 | 46.8%(2,164/4,623) | ≥ 70% | ❌ 未達 |
★★ 此數字不是計法造成的假象:已做對照——若改採業界慣例(排除
*View/*Request/*Response/*Dto/*Config/*Properties等),結果為 行 53.8%/分支 46.7%,
與本節計法幾乎相同(差 0.1 pp)。→ 低覆蓋率源自「真實邏輯缺測試」。
逐模組數據、最低三者與後續建議見docs/08-evidence/N1_工具缺口補齊與量測結果.md §1。
4.3 依賴漏洞掃描(#A-160 新增)
| 項目 | 內容 |
|---|---|
| 後端工具 |
tools/scan_deps.py(OSV;零相依;以 Gradle init script 列舉 runtimeClasspath,不改 repo) |
| 前端工具 | npm audit |
| 判定 | 有 High/Critical 或嚴重度未知 → 退出碼 1(fail-closed) |
| 實測 | 後端 3 CRITICAL+10 HIGH(Tomcat 11.0.24/Jackson 2.21.5·3.1.5);前端 1 HIGH+3 MODERATE |
修補(CR-037,2026-10-05) |
✅ Tomcat 11.0.24 → 11.0.26(3 個 CRITICAL 歸零) |
修補(CR-038,2026-10-05) |
✅ Jackson 2.21.5 → 2.21.7/3.1.5 → 3.1.7(10 個 HIGH 歸零) |
修補(CR-040,2026-10-05) |
✅ 前端 vite 5.4.21 → 8.3.2/react-router-dom 6.30.6 → 7.18.4/@vitejs/plugin-react 4.3.4 → 6.1.1(HIGH 1 → 0;MODERATE 3 → 0) |
| 現況 |
後端 CRITICAL 0/HIGH 0/MODERATE 0(CR-037/CR-038/CR-039)+ 前端 0(CR-040)→ ★★ npm audit 與 OSV 掃描皆 0 命中
|
| 對準出之影響 |
TRP-001 §6.2 第 7 條「安全掃描無高/嚴重漏洞」→ ★ 後端與前端皆達成(⚠️ 滲透測試仍屬外部環境,見 TRP-002 §5) |
⚠️ 升版前須做版本相容性查證(
CSP-006之版本為單一真實來源)——不得直接改版號。
5. 測試資料策略
-
不依賴共享資料庫狀態:每個測試自給自足(arrange 自己建資料),以
@BeforeEach重置或使用
獨立 schema。 - 整合測試用 Testcontainers 起一次性容器,測完即銷毀;禁止連「開發/測試共享真庫」跑單元測試。
-
個資:測試資料一律使用合成卡號(如
4000********1234之測試卡 bin),禁止真實客戶資料。
6. 禁止事項(客戶硬性要求:不得有「假測試」)
| 編號 | 禁止 | 說明 |
|---|---|---|
| T1 |
假斷言:assertTrue(true)、assertEquals(1,1) 之類無效驗證 |
偽裝通過,毫無意義 |
| T2 |
吞失敗:@Test 內 try{...}catch{ /*ignore*/ }
|
隱藏錯誤 |
| T3 |
mock 取代真實邏輯:when(service.calc()).thenReturn(42) 然後只驗證 42 被回傳,卻沒測 calc() 本身 |
應直接對 calc() 做單元測試 |
| T4 |
@Disabled 長期停用而不開 issue 追蹤 |
測試債務累積 |
| T5 |
塞框架:測試類只有 setUp 無任何 @Test
|
空殼(對應 CSP-001 B1) |
| T6 | 依賴執行順序:測試間有隱含次序才過 | 不可重現 |
掃描器:cardcore/tools/scan_faketest.py 於 CI 偵測 T1/T3/T5 模式(如 assertTrue(true)、
verifyNoInteractions 卻無 when、類別無 @Test 方法),命中即紅燈。
7. 與 POC 驗收之連結
BRD-004 之 17 項 POC 情境,每一項對應至少一組 E2E 測試(命名 Poc<NN>_*.test),
其通過狀態直接回寫 GOV-004 追溯矩陣。未通過之 POC 測試,標記 BLOCKED 並開缺陷單(TRP-001 §5)。
8. 交付前自檢清單(Coder 提交 PR 前)
- 單元測試通過、覆蓋率達標
- 無 T1–T6 禁止模式
- 靜態分析 0 error
-
無
TODO/FIXME(CSP-001 B5) -
提交訊息含
Refs: FR/NFR-XXX -
新增 API 已同步
contracts/openapi/*.yaml