iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
IT Operation

AI 輔助開發下,測試如何保住品質防線系列 第 25 篇

Day 25:幫 Refund 補一個例外測試,只花了 13 行

  • 分享至 

  • xImage
  •  

前言:不等新功能當藉口,主動補一個例外測試

第二部看過 commit 80d3469「順路補測試」的真實案例——加新功能時,順便把舊路徑的測試缺口補上。今天反過來,不等有新功能當藉口,直接主動幫 RefundRequest 補一個例外情境測試,看看實際要花多少功夫。

提醒:以下操作都在我自己的本機暫存副本上做示範,不是對真實公開套件的正式修改。

今日目標

  • 找出 RefundRequest 目前欠缺的一個具體例外情境
  • 實際動手補一個測試,驗證它真的能通過
  • 看懂 Omnipay 官方框架已經內建的 validate() 機制,這個套件其實不用自己重寫驗證邏輯
  • 感受「主動補」跟「順路補」在心態上的差別

先找出缺口在哪裡

Day 05 已經核實過,RefundRequestTest 目前只有一個測試方法,只驗證 getData() 回傳的 Action 欄位是不是 'R'。但 RefundRequest::getData() 實際上做了兩件事:

public function getData()
{
    $this->validate('transactionId', 'transactionReference', 'amount');

    return [
        'MerchantTradeNo' => $this->getTransactionId(),
        'TradeNo' => $this->getTransactionReference(),
        'Action' => 'R',
        'TotalAmount' => $this->getAmount(),
    ];
}

第一行的 $this->validate(...) 從沒被測過——如果呼叫端忘了設定 transactionReference(也就是綠界的 TradeNo,要退款的交易編號),會發生什麼事?

官方框架已經內建的驗證機制

validate() 不是這個套件自己寫的,是 Omnipay 官方 omnipay/common 套件在 ParametersTrait 裡提供的通用方法:

// omnipay/common 官方原始碼
public function validate(...$args)
{
    foreach ($args as $key) {
        $value = $this->parameters->get($key);
        if (! isset($value)) {
            throw new InvalidRequestException("The $key parameter is required");
        }
    }
}

邏輯很單純:逐一檢查傳進來的參數名稱有沒有被設定,沒設定就丟出 InvalidRequestException,訊息固定是 "The {參數名} parameter is required"。這代表 RefundRequest 完全不需要自己寫任何驗證邏輯,只要呼叫官方提供的 validate(),行為就已經是確定的——這也是為什麼補這個測試不需要猜測套件裡有什麼特殊邏輯,直接照官方框架的既定行為寫斷言就好。

實際補上去的測試

✅ 新增到 RefundRequestTest.php
public function testMissingTransactionReferenceThrowsException()
{
    $this->expectException(\Omnipay\Common\Exception\InvalidRequestException::class);
    $this->expectExceptionMessage('The transactionReference parameter is required');

    $request = new StubRefundRequest($this->getHttpClient(), $this->getHttpRequest());
    $request->initialize([
        'MerchantID' => '2000132',
        'MerchantTradeNo' => '2821567410556',
        'TotalAmount' => 1000,
        // 故意不設定 TradeNo(對應 transactionReference)
    ]);

    $request->getData();
}

在本機暫存副本上實際跑起來:

$ vendor/bin/phpunit --filter RefundRequestTest --testdox
 ✔ Get data
 ✔ Missing transaction reference throws exception

Tests: 2, Assertions: 3

兩個測試都通過。新增的這個測試方法,加上測試方法之間的空行,總共只花了 13 行程式碼。VoidRequest 因為 extends RefundRequest、共用同一個 validate() 呼叫,可以用完全一樣的模式再補一個對應測試——這裡不重複貼程式碼,邏輯跟上面一模一樣,只是換一個 Stub 類別。

❌ vs ✅:「等新功能上線時順路補」vs「主動排時間補」

❌ 等待型
「這個路徑總有一天會因為別的原因被回頭改到,到時候順便補測試。」
(第二部看到的 commit 80d3469 就是這樣發生的,但那次補到的是 ATM/彈性分期,Refund/Void 至今還沒輪到)
✅ 主動型
「不需要理由,直接找出目前測試覆蓋最薄弱的一段程式碼,排時間補上去。」
(今天這 13 行,不需要等任何新功能當藉口)

兩種方式都能讓覆蓋率變好,差別在於等待型完全看運氣——這個路徑會不會剛好在未來某次改動裡被順路照顧到,沒人能保證。主動型不需要運氣,只需要有人願意主動花 13 行程式碼的時間。測試覆蓋不均的問題,通常不是因為補一個測試很難,而是因為沒有人主動去排這件事的優先順序。

今日思考題

如果請 AI 幫你做「找出這個套件裡測試覆蓋最薄弱的一段程式碼,並補一個例外測試」這件事,你覺得它做得到嗎?它需要什麼資訊才能判斷「薄弱」——這正是明天要討論的 prompt 設計問題。

今日重點回顧

  • RefundRequest::getData() 裡的 validate() 呼叫,從沒被測過
  • validate() 是 Omnipay 官方框架提供的通用機制,行為已經確定,補測試不需要猜測特殊邏輯
  • 實際補上去的例外測試只花 13 行,而且立刻通過
  • 「等新功能上線時順路補」靠運氣,「主動排時間補」不用等任何理由

明日預告

明天把這幾天累積的觀察收斂成一份具體的 CLAUDE.md 規則文字:怎麼要求 AI 在補功能的同時,主動檢查同一個檔案裡還有沒有其他測試覆蓋的缺口。


上一篇
Day 24:把 `check-style` 接進 CI,會先踩到 8 個已知的坑
系列文
AI 輔助開發下,測試如何保住品質防線 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言