「測試資料就是隨便造幾筆,反正跑完測試就丟掉,有什麼好講究的?」
資料本身或許不需要講究,但建立資料的方式,決定了你的程式碼有多容易讀懂。今天要看的是 Laravel Model Factory 的一個實用技巧——用具名的 state 方法,把「這筆資料代表什麼身份」講清楚,而不是每次都手動組一堆屬性。
superAdmin()/admin() 這種具名 state 怎麼設計Laravel 的 Model Factory 用 definition() 方法定義一筆資料的預設樣貌:
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'username' => fake()->unique()->userName(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
}
這定義了一個「一般使用者」該有的樣子——有名字、有信箱、密碼已經雜湊過。單純呼叫 User::factory()->create(),就能造出一筆符合這個樣貌的測試使用者。
這個系統的使用者有角色分級——一般使用者、管理員、最高權限管理員,各自的權限不同。如果每次要造一個「管理員」測試資料,都要手動組出對應的角色關聯,程式碼會變得又長又難懂:
// 不用具名 state,每次都要手動組
$user = User::factory()->create();
$role = Role::factory()->create(['name' => 'Admin']);
$user->roles()->attach($role);
這個系統的做法是在 Factory 裡定義具名的 state 方法:
public function superAdmin(): static
{
return $this->has(Role::factory()->state(['name' => 'Super Admin']));
}
public function admin(): static
{
return $this->has(
Role::factory()->state(['name' => 'Admin'])
);
}
有了這兩個方法,建立測試資料的地方直接寫:
$user = User::factory()->superAdmin()->create();
$user = User::factory()->admin()->create();
這一行程式碼本身就在說明「這是什麼」,不需要額外註解,也不需要讀者自己去推敲「這個 user 關聯了一個叫 Super Admin 的角色,所以他應該是最高權限」——方法名稱直接講出了意圖。has() 是 Laravel Factory 提供的關聯建立方法,這裡的用法是「順便建立一個 Role,並把它關聯到這個 User 上」,一行程式碼同時做完「造角色」跟「建立關聯」兩件事。
$user = User::factory()->create();
$role = Role::factory()->create(['name' => 'Super Admin']);
$user->roles()->attach($role);
三行才能表達一件事,而且讀的人要自己拼湊「這是要造一個最高權限使用者」這個意圖。
$user = User::factory()->superAdmin()->create();
unverified() state 也是同一種思路這個系統的 UserFactory 也保留了 Laravel 內建常見的 unverified() state:
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
unverified() 這種寫法,跟 superAdmin()/admin() 是同一種設計原則的兩種實現方式——unverified() 只是覆寫幾個屬性值(用 state()),superAdmin() 則是額外建立關聯資料(用 has())。不管底層實作是覆寫屬性還是建立關聯,這種設計換來的都是同一件事:呼叫端不用知道「什麼樣的屬性組合才算是一個 XX 身份」,只要呼叫對應名稱的方法就好。
具名 state 方法最適合套用在「有明確業務意義、會被重複用到」的資料狀態——像「這是一個最高權限使用者」「這是一筆已發佈的新聞」,這種狀態會在很多測試案例裡反覆出現,值得抽成一個具名方法。但不是每個屬性組合都值得抽——如果某個特定屬性值只有一支測試會用到一次,直接用 state(['xxx' => 'yyy']) 覆寫就好,抽成具名方法反而增加維護一個很少被重複使用的方法的成本。
你的專案裡有沒有一段建立測試資料的程式碼,每次都要手動組一長串屬性或關聯,才能表達「這是某種特定身份」?如果抽成一個具名的 Factory state 方法,呼叫端會不會變得更好讀?
has() 建立關聯資料superAdmin()/admin() 把「建立某種角色的使用者」封裝成一個具名方法,呼叫端一行程式碼就能表達意圖明天開始進入這個系列的第二部——先從一份 Policy 檔案看起,理解 Laravel 的授權機制怎麼決定「誰能看見什麼」。