PHP Patchwork — 讓老舊專案也能好好寫測試
前言
記錄我在老舊 PHP 專案中導入測試時遇到的問題,以及怎麼用 Patchwork 解決它
在現代 PHP 開發中,寫單元測試幾乎是標配
但如果有開始接手一些「歷史悠久」的老舊專案,就會知道有多痛苦:
- 到處都是
static方法呼叫 - 直接
new依賴物件,沒有注入 - 全域函式散落各處
- 根本無法換掉依賴來做 mock
這時候一般的 Mock 工具(像是 PHPUnit 的 getMockBuilder)可能就不能夠處理這些情況了
因為它們只能 mock 物件,對 static call 和一般函式就束手無策了
Patchwork 是什麼?
Patchwork 是一個 PHP 的函式庫,可以讓你在執行時期「替換」任何函式的實作,包含:
- 一般 PHP 函式(
strlen、time、自定義的 helper 等) static方法- 任何 class 的方法
簡單說,就是可以在測試的時候「偷換」函式行為,而不需要改動原本的程式碼
安裝方式
1 | composer require --dev antecedent/patchwork |
安裝後要注意,Patchwork 需要在 所有程式碼被 autoload 之前 就先載入
例如在測試的 bootstrap 檔案裡手動引入:
1 | // bootstrap.php |
基本用法
替換一個普通函式
老舊專案裡也很常看到這種全域 helper function
例如建立訂單時,直接產生一個訂單編號:
1 | function generateOrderNumber(): string |
如果測試時不想依賴實際時間,就可以直接把這個函式換成固定值:
1 | use function Patchwork\{redefine, restore}; |
替換 Static 方法
這是老舊專案最常見的問題了,因為 static 方法無法被 mock
假設你有這種程式碼:
1 | class OrderService |
可以用 Patchwork 這樣替換:
1 | use function Patchwork\{redefine, restore}; |
搭配 tearDown 自動還原
每次手動 restore 容易忘記,建議在 tearDown 裡統一處理:
1 | protected function tearDown(): void |
使用時的注意事項
- Patchwork 要比 autoload 更早載入,不然無法攔截到相關函式
- 測試結束後記得
restore或restoreAll(),避免影響其他測試 - 這個工具主要適合過渡期使用,長遠來說還是要慢慢重構程式碼
結論
如果專案裡已經大量依賴全域函式,短期內又沒辦法重構,Patchwork 至少能先把測試補起來,讓老舊程式碼的調整有一些簡單的保護,之後再分階段再慢慢處理掉那些難測試的部分