「同一段解析邏輯要處理好幾種輸入格式,測試該怎麼寫才不會變成複製貼上三份幾乎一樣的程式碼?」
這個系統有一個很典型的場景:外部系統回傳的新聞附件資訊,可能是 XML 格式,也可能是陣列格式,這兩種格式還各自有不同的變形。負責解析的類別要把這些格式全部統一轉換成同一種標準結構。今天要講的是這段邏輯的測試怎麼寫——答案是用 Pest 的 dataset 語法,一次把多種輸入變形都測到,不用手動複製貼上。
外部系統傳回的新聞附件資訊,格式並不統一——有時候是 XML 節點(SimpleXMLElement),有時候是已經轉換成陣列的資料,而且陣列格式裡欄位的命名方式在不同時期還不太一樣。負責解析這段資料的類別,要把這些輸入統一轉換成同一種標準結構(例如固定包含網址跟替代文字兩個欄位),提供給前台畫面統一使用。
如果每種格式各寫一支獨立的測試,會出現三、四支結構幾乎一模一樣的測試檔案——建立輸入資料、呼叫同一個解析方法、斷言輸出結構一致,唯一的差異只有輸入資料的格式。這種情況正是 dataset 測試語法要解決的問題。
❌ 每種格式各寫一支重複的測試
test('parses xml format', function () {
$xml = new SimpleXMLElement('<attachment><url>x.jpg</url></attachment>');
$result = NewsAttachment::parse($xml);
expect($result)->toBe(['url' => 'x.jpg', 'alt' => '']);
});
test('parses array format', function () {
$array = ['url' => 'y.jpg', 'description' => 'desc'];
$result = NewsAttachment::parse($array);
expect($result)->toBe(['url' => 'y.jpg', 'alt' => 'desc']);
});
// 還有第三種、第四種格式,各自再複製貼上一次
每支測試的骨架完全一樣,只有輸入資料跟預期輸出不同——複製貼上三、四次之後,如果哪天解析邏輯本身要調整(例如換一種斷言方式),要同步改三、四支測試,很容易漏改。
✅ 用 dataset 把測試邏輯跟測試資料分開
test('parses attachment from various legacy formats', function (mixed $input, array $expected) {
expect(NewsAttachment::parse($input))->toBe($expected);
})->with([
'xml format' => [
new SimpleXMLElement('<attachment><url>x.jpg</url></attachment>'),
['url' => 'x.jpg', 'alt' => ''],
],
'array format with description' => [
['url' => 'y.jpg', 'description' => 'desc'],
['url' => 'y.jpg', 'alt' => 'desc'],
],
// 再加一種新格式,只要加一筆 dataset,不用再寫一次測試邏輯
]);
測試邏輯只寫一次,每一種輸入格式變成 ->with([...]) 裡的一筆具名資料。之後如果外部系統又冒出第五種格式變形,只要在陣列裡加一筆,不用複製貼上整套測試骨架;如果解析邏輯本身要調整斷言方式,也只要改一個地方。
不是所有多輸入情境都適合塞進同一個 dataset。如果不同輸入格式除了資料形狀不同之外,還需要不同的前置設定(例如某種格式需要先 mock 一個完全不同的服務),硬塞進同一個 dataset 反而會讓測試函式裡塞滿條件判斷,變得比拆成獨立測試更難讀。dataset 適合的場景是:測試邏輯完全相同,只有輸入資料跟預期輸出不同——這個附件解析的案例剛好符合這個條件,才值得用 dataset 收斂。
回想你手上專案裡,有沒有幾支測試檔案長得幾乎一模一樣,只是輸入資料不同?如果把它們改寫成 dataset,測試邏輯本身會不會變得更容易一眼看懂測的是同一件事的不同變形?
明天要拆解兩支自製的測試替身——同一份錄影帶,怎麼讓兩支負責不同通訊協定的 Fake 各自挑自己看得懂的部分播放。