iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
Software Development

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

Day 12:這個專案沒有 Policy 的 unit test——為什麼「測行為」有時候比「測實作」更有效

  • 分享至 

  • xImage
  •  

前言

「授權邏輯這麼重要,應該要有專門的 unit test 才對吧?」

這是我一開始也有的直覺:Policy 決定誰能看什麼、誰能做什麼,理論上應該是整個系統裡最需要被獨立測試保護的一層。但實際去翻這個系統的測試檔案時,發現一件出乎意料的事——完全找不到任何一支直接 new UserPolicy()、直接呼叫 Policy 方法斷言回傳值的測試。今天要講的是:這不是漏測,而是一個關於「該測哪一層才真的有效」的判斷。

今日目標

  • 認識這個系統的 Policy 層實際長什麼樣:一層薄薄的委派,不是真正的授權邏輯本身
  • 理解為什麼「單獨測 Policy method 回傳 true/false」意義有限
  • 看懂授權邏輯真正該被驗證的地方是哪一層
  • 建立一個判斷框架:什麼時候該測實作細節,什麼時候該測外顯行為

Policy 只是委派,不是真正的邏輯

打開 app/Policies/UserPolicy.php 這類檔案會發現,裡面幾乎每個方法都只做一件事:把判斷丟給 Spatie Permission 套件的 $user->can('...')。也就是說,Policy 本身沒有自己的業務邏輯,它只是把「這個角色有沒有這個權限」這個問題,轉手交給底層的權限套件去回答。

如果單獨寫一支測試,建立一個 User、指定一個角色、呼叫 $policy->view($user, $model),斷言回傳 truefalse——這支測試驗證到的東西,其實只是「Spatie Permission 套件本身有沒有正確運作」,而不是這個系統自己寫的業務邏輯有沒有正確。真正屬於這個系統的邏輯,藏在另一個地方:Filament Resource 的 getEloquentQuery() 裡,依角色做的查詢過濾。

今天要對照的兩種測試視角

只測 Policy 回傳值

test('admin can view user', function () {
    $admin = User::factory()->admin()->create();
    $target = User::factory()->create();

    expect((new UserPolicy)->view($admin, $target))->toBeTrue();
});

這支測試通過,只證明了「Spatie Permission 套件的 can() 方法運作正常」——如果套件本身有 bug,這支測試也會被連帶測到,但如果系統自己的權限設定表(哪個角色對應哪些權限)設錯了,這支測試完全抓不到,因為它繞過了真實的資料流程,直接手動指定角色。

測「這個角色在畫面上實際看不看得到」

test('non-admin cannot see super admin in user list', function () {
    asAdmin();

    $superAdmin = User::factory()->superAdmin()->create();

    livewire(UserResource\Pages\ListUsers::class)
        ->assertCanNotSeeTableRecords([$superAdmin]);
});

這支測試走的是真實路徑:用 asAdmin() 建立一個真的登入使用者、真的透過 Filament Resource 的查詢邏輯(getEloquentQuery() 裡的角色過濾條件)撈資料、驗證畫面上真的看不到不該看到的那筆資料。這支測試同時涵蓋了 Policy 委派、查詢過濾、還有整個授權鏈路串起來之後的實際結果——任何一個環節設錯,這支測試都會紅。

為什麼「測行為」比「測實作」更有效

單獨測 Policy 方法的回傳值,測的是「這一小段程式碼有沒有照我寫的邏輯執行」;測 Filament Resource 的實際查詢結果,測的是「使用者最終體驗到的行為對不對」。對授權邏輯來說,後者才是真正重要的問題——使用者不會知道你的 Policy 方法回傳了什麼,他們只會知道自己看不看得到某一筆資料、能不能點某一個按鈕。

這不代表 Policy 永遠不需要獨立測試。如果 Policy 裡真的包含自己的判斷邏輯(不只是單純委派),例如「這個角色只能在工作時間內操作」這種跟權限套件無關的規則,那條邏輯就值得獨立測試——判斷標準是「這段程式碼有沒有屬於這個系統自己的業務邏輯」,而不是「這是不是授權相關的程式碼」。

今日思考題

回想你手上專案裡的授權邏輯:有沒有一段程式碼看起來像是「自己的業務規則」,但拆開來看其實只是在轉手呼叫某個套件的方法?如果把它單獨抽出來測,你測到的到底是自己的邏輯,還是套件本身的行為?

今日重點回顧

  • 這個系統完全沒有 Policy class 的 unit test,因為 Policy 只是薄薄一層委派給權限套件
  • 單獨測 Policy 回傳值,測到的多半是套件本身的行為,不是系統自己的邏輯
  • 真正該被驗證的是 Filament Resource 查詢過濾之後,使用者實際看不看得到某筆資料
  • 判斷該測實作還是該測行為,關鍵在於「這段程式碼有沒有屬於這個系統自己的判斷邏輯」

明日預告

明天要講一次架構重構同時帶動測試結構調整的真實案例——表單跟表格的測試檔案,是怎麼從混在一起變成各自獨立的。


上一篇
Day 11:使用者與角色測試——一個權限改動拆成兩支獨立測試檔各自驗證
下一篇
Day 13:表單與表格測試分離——跟著架構重構一起發生的測試結構調整
系列文
同一套 Laravel 系統,測試怎麼寫才不會說謊14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言