「這次改動同時影響了使用者管理跟角色管理兩個頁面,測試要寫在同一支檔案裡嗎?」
今天要看的案例,是這個系統一次真實的權限改動——一次調整同時影響了兩個不同的後台資源頁面,而對應的測試選擇拆成兩支獨立的檔案,各自驗證這次改動在自己身上造成的影響。這個選擇背後的判斷,值得拆開來看。
beforeEach 裡「先確保至少有一筆基礎資料」這個常見的邊界保護寫法這個系統有一次真實的權限調整,目的是限制「最高權限角色」的可見性——不是最高權限身份的使用者,不應該在後台看到、也不應該能夠指派這個角色給任何人。
這個改動實際上同時牽動了兩件事:
這兩件事雖然出自同一次改動、同一個設計意圖,但它們是兩個不同資源頁面各自的授權邏輯——使用者頁面要調整的是「角色選單裡的可選項目」,角色頁面要調整的是「整個列表的可見範圍」。這個系統的測試,對應這兩件事寫了兩支獨立的測試檔案,一支放在使用者資源的測試裡,一支放在角色資源的測試裡。
如果把這次改動的驗證塞進同一支測試檔案,測試檔案的內容會同時涉及「使用者資源頁面的行為」跟「角色資源頁面的行為」——這兩個頁面本質上是不同的 Filament Resource,各自有各自完整的其他測試案例(建立、編輯、刪除、其他欄位驗證)。硬把這次改動的測試塞進其中一支檔案,會讓那支檔案裡出現一段「其實在測另一個資源」的違和內容。
拆成兩支獨立檔案的好處,反映的是一個簡單但容易被忽略的原則:測試檔案的組織方式,應該貼著「被測物件是誰」,而不是貼著「這次改動做了什麼」。一次改動可能同時影響多個資源,但每個資源各自的測試檔案,應該只包含跟這個資源本身相關的測試案例,不管這些案例是不是同一次 commit 裡一起加進去的。
驗證「一般管理員看不到最高權限角色」,這個系統用 Filament 測試工具提供的表格斷言方法:
test('admin cannot see super admin role in the list', function () {
asAdmin();
$superAdminRole = Role::factory()->create(['name' => 'Super Admin']);
$normalRole = Role::factory()->create(['name' => '一般編輯']);
livewire(ListRoles::class)
->assertCanNotSeeTableRecords([$superAdminRole])
->assertCanSeeTableRecords([$normalRole]);
});
assertCanNotSeeTableRecords() 跟 assertCanSeeTableRecords() 這兩個方法,直接對應「這筆特定資料,出不出現在這個角色看到的表格裡」,比自己手動去解析畫面上的 HTML、找特定的文字更明確、也更不容易因為介面文案調整而誤判。測試驗證的是資料層級的可見性,不是介面文字層級的巧合——就算角色名稱顯示文字被改了,這支測試依然準確驗證著「這筆資料到底有沒有出現在這個角色能看到的清單裡」。
同一份改動,在使用者資源那邊的測試,驗證方式類似,但驗證的對象換成「編輯使用者角色時,可選的角色清單裡有沒有排除最高權限角色」——同樣是驗證「誰看得到什麼」,但落在不同的 UI 元件(一個是表格列表,一個是表單裡的選項清單),測試手法也要跟著調整。
這兩支測試檔案的 beforeEach 裡,都有一行先確保至少存在一筆角色資料的設定。這跟 Day 3 提到的「確保至少有一個使用者」是同樣的邏輯考量:如果測試環境完全沒有任何角色資料,某些依賴「至少有一筆角色」的隱性假設可能會意外改變測試行為,例如空清單狀態下的畫面渲染邏輯,或是某些連動邏輯在完全沒有資料時走了跟預期不同的分支。
先確保基礎資料存在,等於先排除掉「空狀態」這個邊界情況對主要測試邏輯的干擾,讓測試能專注在真正要驗證的行為上。
// UserResourceTest.php
test('admin cannot see super admin role option and role list excludes it', function () {
// 同時驗證使用者頁面的角色選單,又驗證角色頁面的列表
// 這支測試檔案原本應該只跟 User Resource 有關
});
這支測試把兩個不同資源的驗證邏輯混在一起,讀這支檔案的人得先搞清楚「這支檔案主要在測什麼」,才能判斷這段跨資源的驗證邏輯放在這裡合不合理。
// UserResourceTest.php
test('role select excludes super admin for non-super-admin editor', function () {
// 只驗證使用者資源這一側的行為
});
// RoleResourceTest.php
test('admin cannot see super admin role in the list', function () {
// 只驗證角色資源這一側的行為
});
每支測試檔案的內容都貼著它所屬的資源,即使兩支測試案例其實對應同一次改動、同一個設計意圖,也不影響各自測試檔案的內聚性。
回想你手上維護的專案,有沒有一次改動同時影響了多個模組,但測試全部塞進了其中一個模組的測試檔案裡?如果現在要找出「這次改動到底測了哪些行為」,你得翻幾支檔案才能拼湊完整?
assertCanNotSeeTableRecords()/assertCanSeeTableRecords() 驗證的是資料層級的可見性,比解析畫面文字更明確、更不容易被介面調整誤判beforeEach 裡先確保基礎資料存在,是排除「空狀態」邊界情況干擾主要測試邏輯的常見手法明天要講一件乍看有點反直覺的事:這個系統完全沒有針對 Policy 類別本身寫過任何 unit test——這是疏漏,還是一個有道理的測試策略選擇?