「這支測試把外部依賴都 mock 掉了,應該很單純、很穩定吧?」
昨天談到 AI 傾向把待測物件依賴的每一個東西都 mock 掉,理由聽起來很合理——隔離、快、不受外部環境影響。但「隔離」跟「隔離過頭」中間只有一線之隔,今天用一個具體案例,示範這條線被跨過去之後,測試會變成什麼樣。
假設有一個 OrderService,下單時要呼叫 PaymentGateway 完成扣款:
❌ 過度 mock 的測試:
class OrderServiceTest extends TestCase
{
public function test_creates_order_when_payment_succeeds(): void
{
$paymentGateway = $this->createMock(PaymentGateway::class);
$paymentGateway->method('charge')->willReturn([
'status' => 'success',
'id' => 'txn_123',
]);
$orderService = new OrderService($paymentGateway);
$order = $orderService->create($cart = new Cart(['amount' => 1000]));
$this->assertSame('paid', $order->status);
}
}
這支測試看起來完全合理:mock 掉外部依賴、驗證訂單狀態變成 paid。它會通過,CI 也會綠燈。
問題出在 willReturn([...]) 這一行——AI 憑著對「付款成功大概長什麼樣」的直覺,自己編了一個回傳格式:status 是字串 'success',id 是交易編號。但真實的 PaymentGateway API 回傳的格式其實是巢狀的:
{
"result": { "code": 0, "message": "OK" },
"transaction": { "reference": "txn_123" }
}
OrderService::create() 內部讀的是 $response['result']['code'] === 0 跟 $response['transaction']['reference'],不是 mock 裡編的那兩個扁平欄位。實際串接到真實付款閘道時,OrderService 讀不到預期的欄位,觸發例外,訂單建立失敗——但這整個落差,在測試裡完全沒有機會被看見,因為 mock 回傳的假資料,從一開始就沒有依據真實契約去對齊。
這正是過度 mock 最陰險的地方:測試的綠燈,只證明了「待測程式碼在面對這個 mock 訂出的假設下行為正確」,完全沒有驗證「這個假設本身是不是真的」。 待測物件(OrderService)跟它依賴的外部系統(PaymentGateway)之間的契約,一旦被 mock 取代,這個契約有沒有被正確理解,就變成一個測試完全看不見的黑洞。
AI 在寫這種 mock 時,通常不是刻意編造,而是基於「看起來合理」的直覺——status: success 讀起來就是個成功的付款結果,語意上沒有錯,但語意合理不代表格式正確。AI 缺乏「我現在編的這個回傳值,有沒有真的對照過官方文件或真實回應」這個查證動作的內建提醒,而人類工程師在寫這種 mock 時,通常會下意識想到「這個格式我是猜的還是查的」。
用一組對照來看正確的做法:
✅ 對齊真實契約的 mock:
class OrderServiceTest extends TestCase
{
public function test_creates_order_when_payment_succeeds(): void
{
$paymentGateway = $this->createMock(PaymentGateway::class);
$paymentGateway->method('charge')->willReturn(
// 格式直接複製自付款閘道官方文件的成功回應範例,
// 或是從一次真實呼叫的回應錄下來的 fixture
[
'result' => ['code' => 0, 'message' => 'OK'],
'transaction' => ['reference' => 'txn_123'],
]
);
$orderService = new OrderService($paymentGateway);
$order = $orderService->create($cart = new Cart(['amount' => 1000]));
$this->assertSame('paid', $order->status);
}
}
→ 差別不在「有沒有 mock」,在「mock 出來的資料是不是真的對照過契約」。
真正該檢查的問題,不是「這裡該不該 mock」(Day13 已經講過,外部依賴通常都該 mock),而是:
回想你手上被 mock 掉的外部依賴:那個 mock 回傳的資料,你是從真實文件或真實回應複製過來的,還是憑印象編的?如果答案是後者,這支測試給你的安全感,可能比你以為的還要薄弱。
明天要往前一步:AI 除了容易 mock 過頭,也容易只測 happy path——邊界情境跟例外狀況,往往要人主動要求才會被想到。