「明明只是測首頁能不能正常打開,為什麼 beforeEach 要設定這麼多東西?」
首頁通常是一個系統裡最「熱鬧」的頁面——同時組合了好幾個不同來源的內容區塊,也最容易依賴一堆橫跨全站的設定值。今天要拆這個系統的首頁測試,看它的 beforeEach 準備了什麼、每個測試各自驗證哪個區塊,藉此討論「頁面測試的相依範圍該怎麼控制在合理大小」這個問題。
beforeEach 準備了哪些全站層級的設定beforeEach(function () {
$settings = app(GeneralSettings::class);
$settings->site_name = '示範大學';
$settings->related_links = [];
$settings->sso_login = true;
$settings->save();
});
這個系統用 spatie/laravel-settings 套件管理橫跨全站的設定值(跟 Filament 後台可以編輯的設定頁綁在一起)。首頁的 layout 會用到站台名稱之類的全站設定,所以每一支首頁測試開跑前,都要先準備好這幾個設定值,不然頁面在渲染的過程中可能會因為缺少必要設定而出錯。光是這三行 beforeEach,就已經在告訴你:這是一個「相依範圍」不小的測試——首頁不是一個獨立的功能,它天生就跟好幾項全站層級的狀態綁在一起。
準備好全站狀態之後,實際的測試案例反而寫得很聚焦,每一支只驗證首頁上的一個區塊:
test('displays headline news from category 7', function () {
$news = News::factory()->count(3)->create([
'news_category_id' => 7,
'published' => true,
'published_at' => now()->subDay(),
]);
$response = get('/');
$response->assertOk();
foreach ($news as $item) {
$response->assertSee($item->title);
}
});
test('headline displays published webcasts', function () {
Webcast::factory()->count(3)->create(['published' => true]);
Webcast::factory()->count(2)->create(['published' => false]);
$response = get('/');
$response->assertOk();
});
第一支只關心「頭條新聞區塊有沒有正確顯示指定分類的新聞」,第二支只關心「已發布的影音頻道會出現、未發布的不會」——**每支測試只針對首頁上的一個區塊建立測試資料、只驗證那個區塊的行為,不會在同一支測試裡把所有區塊的資料都準備齊全再一次驗證完。**這樣拆開的好處是,當某支測試失敗,你能立刻知道是首頁的哪個區塊出了問題,不用在一支塞滿各種資料的巨型測試裡大海撈針。
首頁的頭條區塊有一套排序優先序:新聞排在影音之前、置頂內容排在一般內容之前、同一層級內按發布時間新到舊排序。這個系統把這套規則拆成三支獨立的測試各自驗證:
test('headline sorts news before webcasts', function () {
$webcast = Webcast::factory()->create(['title' => '活動快訊', 'published' => true]);
$news = News::factory()->create([
'title' => '頭條新聞',
'news_category_id' => NewsCategory::HEADLINE_ID,
'published_at' => now()->subDay(),
]);
$response = get('/');
$response->assertSeeInOrder(['頭條新聞', '活動快訊']);
});
test('headline sorts on_top news first', function () {
$normal = News::factory()->create([
'title' => '一般新聞', 'on_top' => false, 'published_at' => now(),
]);
$pinned = News::factory()->create([
'title' => '置頂新聞', 'on_top' => true, 'published_at' => now()->subWeek(),
]);
$response = get('/');
$response->assertSeeInOrder(['置頂新聞', '一般新聞']);
});
test('headline sorts by published_at desc within same on_top', function () {
// 同層級內,發布時間新到舊排序
});
用 assertSeeInOrder() 直接驗證畫面上文字出現的先後順序,而不是去解析回應內容、比對陣列——這是一個很實用的斷言技巧:排序規則的驗證,不需要拆到「資料庫查詢回傳的陣列順序對不對」這麼底層,只要確認畫面上呈現的順序符合預期就夠了,因為使用者真正在意的就是畫面上看到的順序。
三支測試各自只操弄「排序規則裡的其中一個維度」(類型優先、置頂優先、時間排序),刻意不把三個維度混在同一支測試裡一次驗證——如果混在一起,一旦測試失敗,你很難判斷是哪一條排序規則出了問題。
首頁測試示範了兩個層次的「相依範圍控制」:全站層級(beforeEach 準備必要的全站設定,讓每支測試不用重複這段樣板)跟單一測試層級(每支測試只準備並驗證跟自己相關的那一小塊資料,不貪心地想一次驗證全部)。組合式頁面測試最容易犯的錯誤,是每支測試都想著「反正首頁很多東西,乾脆都準備齊全一起測」——這樣寫出來的測試會又慢又難讀,失敗時也不容易定位問題。把相依範圍控制在「這支測試真正需要的最小狀態」,才是讓組合頁面測試維持可讀性的關鍵。
回想你維護的專案裡,有沒有一支「組合式頁面」的測試,beforeEach 或測試本體裡準備了一堆跟這支測試實際要驗證的行為無關的資料?如果拆掉這些不相關的準備工作,這支測試會不會變得更好懂?
beforeEach 準備了全站層級的設定值,反映出組合頁面天生的相依範圍assertSeeInOrder() 直接驗證畫面呈現順序,不需要拆到底層資料結構明天是這個系列的最後一天,要把前面 29 天拆過的所有測試層次收攏起來,回頭看這 63 支測試教會我的分層策略——包含幾個誠實的取捨跟沒有做到位的地方。