「現在 AI 這麼厲害,寫測試不是應該叫 AI 順手補一補就好了嗎?」
如果你也這樣想過,這個系列想用一個真實存在、你可以直接打開來看的公開 PHP 套件,回答這個問題——不是空談「AI 寫的測試不可靠」這種老生常談,而是給你看具體發生過什麼事。
素材是 omnipay-taiwan/omnipay-ecpay,一個讓 Omnipay 支付抽象層可以接上台灣綠界金流(ECPay)的驅動套件,我自己維護。這個系列會直接翻它的 commit 歷史、測試檔案、CI 設定,找出真實的品質防線長什麼樣子——包括防線上真實存在的破洞。
讀完這篇你會知道:
跟我另一個系列(用一套去識別化的 legacy 系統當素材)不一樣,這次刻意選一個任何人都能打開來看的公開專案。原因很直接:品質防線這種東西,講得再抽象都不如直接把 commit diff 攤開來看。你現在就可以打開瀏覽器去對照我接下來每一篇提到的檔案跟行號,這是去識別化素材做不到的事。
也因為這樣,這個系列不能唬爛——每個論點都要能被你反查證實。
先講幾個核實過的數字,讓你有個底:
phpunit 時會執行 47 個測試——另外 26 個是從 Omnipay 官方 GatewayTestCase 基底類別自動繼承來的泛用測試(例如「每個 Gateway 都要能回傳非空名稱」「預設參數要有對應的 getter/setter」),這是這系列後面會提到的一個重點:用官方測試框架的基底類別,可以不用自己動手就先擋住一整層最基本的規範phpunit.xml 裡設定了完整的覆蓋率報表輸出(clover、HTML、text),但 CI 實際執行時用 --no-coverage 把它關掉了光看這四個數字,你大概已經猜到這個系列想講什麼:這個套件不是沒有品質工具,是工具擺在那裡沒有真的被用起來守門。覆蓋率設定存在,但沒人在 CI 上盯著數字;測試存在,但覆蓋不均——有些付款方式測得很細,有些只測了一種情境。
這個套件的 README 現在還停留在 Omnipay 官方骨架套件的範本文字:
❌ README.md 目前的樣子
## Usage
The following gateways are provided by this package:
* ecpay
一行字,沒有告訴你這個套件實際支援信用卡、ATM、超商代碼/條碼、電子發票、還是無卡分期這些付款方式裡的哪幾種。
但如果你去看 tests/Message/PurchaseRequestTest.php,會看到:
✅ 測試案例反而說明了真正的功能範圍
public function testGetData() { /* ChoosePayment = Credit:信用卡 */ }
public function testATMGetData() { /* ChoosePayment = ATM */ }
public function testBNPLGetData() { /* ChoosePayment = BNPL:無卡分期 */ }
public function testFlexibleInstallmentGetData() { /* CreditInstallment = '30N':信用卡彈性分期 */ }
測試案例比 README 更誠實地告訴你這個套件能做什麼——這是這系列會反覆出現的一個現象:文件會過期、會被忘記更新,但測試如果寫得夠具體,會逼著你在改程式碼的同時面對「這個情境還成立嗎」的問題。這不是說測試可以取代文件,而是說當文件跟程式碼脫鉤時,測試往往是唯一還跟得上真相的東西。
分四個階段,全部對應到這個套件裡已經核實過的真實案例,不是憑空設計的教學情境:
80d3469),看清楚「加新功能」跟「補測試」在真實開發節奏裡是怎麼交織的——不是教科書寫的「先寫測試再寫功能」那麼乾淨,但也不是完全沒紀律你自己維護或參與過的專案裡,有沒有「工具設定了但沒人真的在看」的東西?可能是覆蓋率報表、可能是 CI 裡一個從沒被呼叫過的 lint script。先想一下,這系列後面會直接示範怎麼把這種「擺著好看」的防線變回真的有用。
omnipay-taiwan/omnipay-ecpay
明天會看這個套件怎麼用 Trait 組合出 12 種不同的付款方式欄位,以及這種設計在「多人共用的套件」跟「我自己接手的系統」之間,會遇到不一樣的取捨。
寫在最後:維護一個公開套件跟接手一套內部系統,最大的差別是「你看不到誰在用它」。你補的每個測試、關掉的每個覆蓋率警告,都是在替一群你素未謀面的使用者把關。這系列接下來會誠實地帶你看,這件事我自己也還沒做得完美——這正是我想把它寫成系列的原因。