iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Software Development

同一套 Laravel 系統,測試怎麼寫才不會說謊系列 第 23

Day 23:PathGenerator 本身怎麼測——只驗證「這個 Model、這個 ID,該產生什麼路徑」

  • 分享至 

  • xImage
  •  

前言

「昨天不是已經測過遷移指令了嗎?今天還要再測一次檔案路徑?」

昨天測的是「遷移指令這整個流程」——它能不能正確找到舊格式的檔案、能不能正確搬到新路徑。但如果搬移邏輯本身沒問題,新上傳的檔案卻從一開始就沒有照著自訂規則產生正確路徑呢?昨天的測試不會告訴你答案,因為它假設的前提是「上傳流程本身是對的」。今天要拆的是更小的一個單位:自訂的路徑產生規則本身,到底對不對。

今日目標

  • 理解「測整個流程」跟「測流程裡的一個小單位」是兩件不同的事,不能互相取代
  • 看一個自訂 PathGenerator 怎麼被獨立測試,不牽扯任何遷移邏輯
  • 學會用多個不同 Model 驗證同一個規則,確認規則不是只在特例下剛好成立
  • 認識「先確定小單位正確,再測包住它的整個流程」這個測試策略的層次感

為什麼要把 PathGenerator 單獨拉出來測

這個系統自訂了一個 ModelPathGenerator,取代 Media Library 套件預設的路徑規則,讓上傳的檔案依照 Model 類型跟 ID 分資料夾(例如某個 Model 底下的圖片存在 對應資料夾/{model_id}/{media_id}/檔名 這樣的結構),方便直接從路徑推算出這個檔案屬於哪一筆資料,不用另外查資料庫。

昨天的遷移指令測試,關心的是「舊資料能不能被正確找到並搬移」,測試的起點就假設了「新上傳的檔案,套件會照著自訂規則產生正確路徑」——但這個假設本身有沒有錯,昨天的測試回答不了,因為它的重點是搬移邏輯,不是路徑產生邏輯。

如果你把「大流程對不對」跟「流程裡每個小單位對不對」混在同一組測試裡驗證,一旦測試失敗,你很難第一時間知道問題出在哪一層。

這支測試在驗證什麼

beforeEach(function () {
    Storage::fake('public');
    config(['media-library.path_generator' => ModelPathGenerator::class]);
});

test('Banner upload stores file under 對應目錄/{model_id}/{media_id}/', function () {
    $banner = Banner::factory()->create();

    $banner->addMedia(UploadedFile::fake()->image('hero.jpg'))
        ->toMediaCollection('default');

    $media = $banner->getFirstMedia();

    $expectedPath = "banner/{$banner->id}/{$media->id}/hero.jpg";
    Storage::disk('public')->assertExists($expectedPath);
});

這支測試只做一件事:真的對一個 Banner Model 呼叫 addMedia()->toMediaCollection(),然後檢查磁碟上有沒有出現預期路徑的檔案。它完全不管遷移指令、不管舊資料,單純驗證「這個 Model、上傳這個檔案,最後產生的路徑對不對」。

同一份測試檔案裡,同樣的驗證邏輯,會重複套用在另外兩種不同的 Model 上:

test('News upload stores file under 對應目錄/{model_id}/{media_id}/', function () {
    $news = News::factory()->create();
    $news->addMedia(UploadedFile::fake()->image('photo.png'))->toMediaCollection('images');

    $media = $news->getFirstMedia('images');
    Storage::disk('public')->assertExists("news/{$news->id}/{$media->id}/photo.png");
});

test('Webcast upload stores file under 對應目錄/{model_id}/{media_id}/', function () {
    $webcast = Webcast::factory()->create();
    $webcast->addMedia(UploadedFile::fake()->image('thumb.jpg'))->toMediaCollection('default');

    $media = $webcast->getFirstMedia();
    Storage::disk('public')->assertExists("webcast/{$webcast->id}/{$media->id}/thumb.jpg");
});

為什麼要對三種不同的 Model 各測一次

如果只測一種 Model(例如只測 Banner),這條規則可能只是「剛好在這個 Model 上成立」,沒辦法確定它是不是真的「對所有 Model 都適用的通用規則」。分別對 BannerNewsWebcast 三種完全不同的 Model 各測一次,才能確認這個 PathGenerator 的邏輯是根據 Model 類型動態產生的,不是寫死了某一種特例。

同一支測試檔案裡,還額外驗證了兩件跟「路徑格式」有關、但角度不同的事:

  • getUrl() 回傳的網址是不是真的包含了正確的路徑片段(不只是磁碟上存在這個檔案,套件回傳給前端使用的網址也要對)
  • 同一筆資料上傳多張圖片時,每一張圖片是不是都各自產生獨立、正確的路徑,不會互相覆蓋

這個層次感給我們的啟示

把這篇跟昨天的遷移指令測試放在一起看,會看到一個清楚的分工:

  • 今天這支測試回答:「這個 Model、這一筆資料,上傳檔案後該產生什麼路徑?」——一個小單位的行為對不對
  • 昨天那支測試回答:「假設路徑規則是對的,遷移指令能不能正確把舊格式資料搬到新格式?」——一整個流程對不對

**先確定小單位本身正確,再測包住它的整個流程,這樣當某個測試失敗時,你能立刻知道問題出在「規則本身錯了」還是「規則套用的流程錯了」,不用每次都從頭排查整條路徑。**如果只寫了昨天那支遷移指令測試,沒有今天這支,一旦路徑規則本身壞掉,你只會看到遷移指令測試失敗,卻不容易第一眼看出問題根源在路徑規則,還是在搬移邏輯本身。

今日思考題

回想你維護的專案,有沒有一個小單位的邏輯(例如一個格式轉換函式、一個路徑產生規則)從來沒有被獨立測試過,只靠包住它的大流程測試間接覆蓋?如果那個小單位本身壞了,現有的測試多久之後才會發現?

今日重點回顧

  • 「測整個流程」跟「測流程裡的一個小單位」回答的是不同問題,不能互相取代
  • 自訂的 PathGenerator 被獨立拉出來測,完全不牽扯任何遷移邏輯,只驗證「這個 Model、上傳這個檔案,該產生什麼路徑」
  • 對三種不同的 Model 各測一次,確認規則是通用的,不是只在特例下剛好成立
  • 先確定小單位本身正確,再測包住它的整個流程,測試失敗時才能快速定位問題出在哪一層

明日預告

明天要換一個完全不同的主題:時間相依的邏輯怎麼測。一支會依賴「現在幾點」來決定行為的 Job,如果不特別處理,測試可能在某些日期跑會過、某些日期跑會壞。


上一篇
Day 22:拿掉一支「假」的 unit test——為什麼真實整合測試才可信
系列文
同一套 Laravel 系統,測試怎麼寫才不會說謊23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言