昨天說「順路補測試」是一種真實存在的償債機制,但它有個前提:得先有人因為別的理由打開那個檔案。今天看這個套件裡兩個一直沒等到這個機會的類別——RefundRequest 跟 VoidRequest。
RefundRequest、VoidRequest 的測試現況(延續 Day 05 提過的數字,這次換角度討論「為什麼」)RefundRequestTest、VoidRequestTest 各自只有一個測試方法,都只驗證 Action 欄位有沒有帶對值:
// RefundRequestTest.php
public function testGetData()
{
// ...
self::assertEquals('R', $data['Action']);
self::assertEquals('1000', $data['TotalAmount']);
}
// VoidRequestTest.php,結構幾乎一樣,只是 Action 換成 'N'
沒有測試涵蓋:金額為 0 或負數會怎樣、官方 SDK 拋出的 RtnException 會不會被正確轉譯、TotalAmount 傳入非數字字串時的行為。這裡要修正一個容易誤解的地方——RtnException 不是「退款/作廢業務失敗」專屬的例外:讀過官方 SDK 原始碼後確認它是 SDK 層級的通用例外,涵蓋 CURL 連線失敗、CheckMacValue 產生/驗證失敗等情境,RefundRequest/VoidRequest 用的是不驗證回應簽章的 PostWithCmvEncodedStrResponseService(見 Day 03),所以這裡實際會遇到的多半是連線層級的失敗,不是「退款被拒絕」——那種業務層失敗,綠界是透過回應裡的 RtnCode/RtnMsg 表達,根本不會讓 SDK 丟例外。這條路徑目前完全沒有測試涵蓋,這些都是「例外/邊界情境」,跟 Day 05 提過的 testInvalidCheckMacValue(安全關鍵路徑)屬於同一類——目前完全沒被驗證。
回頭看這個套件的功能演進歷程:付款方式(信用卡、ATM、超商代碼、BNPL、彈性分期)一直在擴充,PurchaseRequest 因此被反覆打開、反覆修改,每次修改都製造了一次「順便看看旁邊缺什麼」的機會。
RefundRequest、VoidRequest 的介面相對單純——退款、作廢基本上就是送出商店交易編號跟金額,沒有「這次要新增哪種退款方式」這種需求會迫使開發者反覆回來修改這兩個檔案。沒有需求驅動的修改,就沒有「順便補測試」的觸發時機。
這是一個合理的推測,不是這個套件維護紀錄裡明講的動機——但从 commit 歷史(Refund、Void 各自的功能程式碼一次寫完之後幾乎沒再被動過)跟測試現況(維持著最初、只驗證 Action 欄位的樣子)對照來看,這個解釋是說得通的。
❌ 誤讀:覆蓋多寡代表重要性排序
"Purchase 測得細,Refund/Void 測得鬆,
代表 Purchase 比較重要,退款作廢比較不重要"
→ 這個推論很危險:退款失敗對使用者造成的影響
通常比訂單查詢更直接、更急迫
✅ 更準確的理解:覆蓋多寡反映的是機會分布
"Purchase 因為功能一直在擴充,反覆被打開,
每次都製造一次補測試的機會;
Refund/Void 介面穩定,很少被打開,
自然也很少有機會被順路照顧到"
→ 覆蓋不均不等於重要性排序,
而是「誰比較常被打開」的副產品
**測試覆蓋不均,反映的往往不是這段程式碼有多重要,而是它有多常被人重新打開。**這句話比昨天的結論更進一步——它說明了為什麼光靠「順路補測試」這個機制,永遠會有一批穩定卻沒被好好驗證的程式碼被留在原地,因為它們穩定到沒有人有理由回頭看它。
你的專案裡有沒有一個「很穩定、很少被改、但其實測試很薄弱」的模組?它會不會剛好是因為太穩定了,反而沒有機會被順路補上測試?
RefundRequest、VoidRequest 至今只各有一個測試方法,只驗證欄位值,沒有例外/邊界情境明天把視角轉到 AI 協作上:如果請 AI 幫忙加一個新功能,AI 有沒有機會、該不該被要求,主動去注意這種「因為太穩定而被冷落」的測試缺口?