iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Software Development

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

Day 08:Feature 測試基礎——一個 HTTP 請求怎麼測到「畫面對不對」

  • 分享至 

  • xImage
  •  

前言

「這個頁面回傳 200,測試就算過了嗎?」

這是很多人寫 Feature 測試時,不知不覺會停下來的地方——狀態碼是 200,代表伺服器沒有拋出例外、路由正常運作,聽起來已經是個不錯的訊號。但今天要說的是:「回應成功」跟「回應了正確的內容」之間,還有一段沒被驗證到的距離,而這段距離,往往才是使用者真正在乎的部分。

今日目標

  • 理解 Feature 測試最基礎、也最容易被誤用的一層驗證:狀態碼
  • 看這個系統怎麼從「狀態碼對不對」進階到「畫面上真的顯示了該顯示的資料」
  • 認識兩種斷言力度分別適合驗證什麼情境
  • 建立一個習慣:寫 Feature 測試前先問「使用者真正在乎的是什麼」

第一層:路由能不能到達

Feature 測試最基本的用法,是模擬一次真實的 HTTP 請求,檢查伺服器有沒有正常回應:

test('loads successfully', function () {
    get('/')
        ->assertOk();
});

這支測試驗證的事情很單純:首頁這個路由存在、沒有因為程式碼錯誤而拋出例外、回應狀態碼是 200。這一層驗證的價值不能小看——它能在最早期抓出「這支路由整個掛掉了」這種等級的問題,例如某次改動不小心把路由定義刪掉、Controller 建構子拋出例外、View 檔案找不到。這些問題如果沒有測試守著,往往要等到有人真的打開瀏覽器才會發現。

但這層驗證的極限也很明顯:一個回傳 200 的頁面,內容可以是完全空白、可以是顯示了錯誤的資料、可以是漏掉了某個區塊——只要沒有拋出例外,狀態碼永遠是 200。狀態碼驗證不了「這個頁面是不是真的在做它該做的事」。

第二層:畫面上有沒有出現該出現的資料

要驗證「這個頁面真的顯示了我要的資料」,需要更進一步的斷言。這個系統的做法是先準備好測試資料,再檢查回應內容裡有沒有出現這筆資料該有的文字:

test('displays headline news from category', function () {
    $news = News::factory()->create([
        'news_category_id' => 7,
        'title' => '測試用新聞標題',
    ]);

    get('/')
        ->assertSee($news->title);
});

這支測試多做了一件事:先用 Factory 建立一筆有明確身份(特定分類、特定標題)的測試資料,再檢查首頁回應內容裡有沒有出現這個標題。這一層驗證回答的問題,從「這個頁面能不能被訪問」,變成「這個頁面有沒有正確查出並顯示我要驗證的那批資料」——如果查詢條件寫錯、關聯資料抓錯、View 迴圈邏輯漏掉某個區塊,這支測試都會失敗,而純看狀態碼的測試完全抓不到這些問題。

兩層驗證,各自適合什麼情境

把這兩種寫法放在一起看,可以整理出一個實用的判斷:

  • 只驗證狀態碼,適合用在「這個頁面存在、能被正常訪問」本身就是驗證重點的情境——例如你只是想確認一個新增的路由沒有寫錯路徑、沒有漏掉中介層設定,這種情境下狀態碼驗證已經足夠,硬要進一步驗證畫面內容反而會讓測試變得脆弱,跟這支測試真正想確認的事情無關。
  • 需要驗證畫面內容,適合用在「這個頁面的核心價值,是正確顯示某批資料」的情境——例如首頁的新聞列表、任何跟資料查詢/篩選有關的頁面,這種情境下只驗證狀態碼,等於完全沒有驗證到這個頁面存在的意義。

判斷的關鍵問題是:如果這個頁面的核心邏輯壞掉了(查詢條件錯、資料沒顯示、顯示錯資料),狀態碼會不會因此改變?如果答案是不會,那狀態碼驗證對這個情境來說就是不夠的

❌ 只驗證狀態碼,卻自認驗證了功能

test('news list page works', function () {
    News::factory()->count(5)->create(['news_category_id' => 1]);

    get('/news')
        ->assertOk();
});

這支測試的名字叫「新聞列表頁能運作」,但它實際驗證的只有「這個路由回應 200」。就算查詢條件寫錯,把所有新聞都篩選掉、畫面上一筆資料都沒顯示,這支測試依然會綠燈通過——測試的名字給了錯誤的安全感。

✅ 驗證真正該驗證的行為

test('news list displays news from the given category', function () {
    $matching = News::factory()->create(['news_category_id' => 1, 'title' => '分類內新聞']);
    $other = News::factory()->create(['news_category_id' => 2, 'title' => '其他分類新聞']);

    get('/news?category=1')
        ->assertOk()
        ->assertSee($matching->title)
        ->assertDontSee($other->title);
});

這支測試不只驗證狀態碼,還驗證了「符合條件的資料有顯示」跟「不符合條件的資料沒有出現」——後者容易被忽略,但同樣重要:一個篩選功能如果篩選條件完全失效、顯示了所有資料,光看「該顯示的資料有沒有出現」是抓不出來的,只有同時驗證「不該出現的有沒有被排除」,才能真正證明篩選邏輯有效。

今日思考題

回想你手上維護的 Feature 測試套件,有沒有一支測試的名字聽起來驗證了某個具體功能,但實際上只驗證了狀態碼?如果現在幫它補上內容驗證,你有多大把握它會一次就通過?

今日重點回顧

  • 只驗證狀態碼的測試,只能抓出「路由整個掛掉」這種等級的問題,抓不出資料查詢、顯示邏輯的錯誤
  • 驗證畫面內容需要先準備有明確身份的測試資料,再檢查回應內容裡有沒有出現該有的文字
  • 判斷該用哪一層驗證的關鍵問題:「如果這裡的核心邏輯壞了,狀態碼會不會因此改變?」
  • 篩選類功能的測試,除了驗證「該出現的有出現」,也要驗證「不該出現的有沒有被排除」,兩者缺一不可

明日預告

明天要進到 Filament 後台的測試手法——測後台跟測一般前台頁面的驗證方式不一樣,這篇只講怎麼測,不深入教 Filament 本身怎麼開發。


上一篇
Day 07:一次排序邏輯的回歸——Feature 測試補上後才抓到的舊 bug
下一篇
Day 09:Filament 後台測試怎麼寫——測「動作有沒有真的發生」而不是「畫面長什麼樣」
系列文
同一套 Laravel 系統,測試怎麼寫才不會說謊14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言