Factory 与 Faker 测试数据生成
High Contrast
Dark Mode
Light Mode
Sepia
Forest
1 min read158 words

Factory 与 Faker 测试数据生成

好的 Factory 是写出高质量测试的基础。Laravel 的 Factory + Faker 能生成真实感的数据:随机姓名、Email、日期、甚至符合业务逻辑的状态组合。


创建 Factory

php artisan make:factory TaskFactory --model=Task
<?php
// database/factories/TaskFactory.php
namespace Database\Factories;
use App\Models\Task;
use App\Models\User;
use App\Models\Project;
use Illuminate\Database\Eloquent\Factories\Factory;
class TaskFactory extends Factory
{
protected $model = Task::class;
public function definition(): array
{
$statuses = ['pending', 'in_progress', 'done', 'cancelled'];
$status   = $this->faker->randomElement($statuses);
return [
'user_id'      => User::factory(),  // 自动创建关联用户
'project_id'   => null,
'title'        => $this->faker->sentence(nb_words: 4, variableNbWords: true),
'description'  => $this->faker->optional(0.7)->paragraphs(2, true),
'status'       => $status,
'priority'     => $this->faker->numberBetween(1, 5),
'due_date'     => $this->faker->optional(0.6)->dateTimeBetween('now', '+60 days'),
'completed_at' => $status === 'done' ? $this->faker->dateTimeBetween('-30 days', 'now') : null,
'is_pinned'    => $this->faker->boolean(20),  // 20% 概率为 true
];
}
// 状态方法:覆盖特定字段
public function pending(): static
{
return $this->state([
'status'       => 'pending',
'completed_at' => null,
]);
}
public function done(): static
{
return $this->state([
'status'       => 'done',
'completed_at' => now()->subDays(rand(1, 30)),
]);
}
public function highPriority(): static
{
return $this->state(['priority' => 5]);
}
public function overdue(): static
{
return $this->state([
'status'   => 'pending',
'due_date' => $this->faker->dateTimeBetween('-30 days', '-1 day'),
]);
}
public function forUser(User $user): static
{
return $this->state(['user_id' => $user->id]);
}
public function withProject(): static
{
return $this->state(['project_id' => Project::factory()]);
}
}

常用 Faker 方法速查

// 字符串
$faker->word                          // 'consectetur'
$faker->sentence(6)                   // 'A quick brown fox jumps.'
$faker->paragraph(3)                  // 多段文字
$faker->name()                        // 'John Doe'
$faker->firstName('male')             // 'James'
$faker->lastName()                    // 'Smith'
$faker->email()                       // 'user@example.com'
$faker->safeEmail()                   // 'user@example.org'(测试安全域名)
$faker->url()                         // 'https://www.example.com/path'
$faker->slug()                        // 'hello-world-foo'
// 数值
$faker->numberBetween(1, 100)
$faker->randomFloat(2, 0, 999.99)     // 2 位小数
$faker->randomElement(['a', 'b', 'c'])
// 日期时间
$faker->dateTime()                    // DateTime 对象
$faker->dateTimeBetween('2020-01-01', 'now')
$faker->dateTimeThisYear()
$faker->date('Y-m-d')                 // '2024-03-22'
// 可选值(有概率为 null)
$faker->optional(0.5)->email()        // 50% 概率返回 null
$faker->optional(0.8)->sentence()     // 80% 返回句子,20% 返回 null
// 本地化(中文)
use Faker\Factory as FakerFactory;
$faker = FakerFactory::create('zh_CN');
$faker->name()     // '张伟'
$faker->address()  // '北京市朝阳区...'
$faker->company()  // '科技有限公司'

在测试和 Seeder 中使用

// tests/ 目录中使用
// 创建单个模型
$task = Task::factory()->create();
$task = Task::factory()->pending()->create();
$task = Task::factory()->highPriority()->overdue()->create();
// 创建多个
$tasks = Task::factory()->count(10)->create();
$tasks = Task::factory()->count(5)->pending()->create();
// 创建但不写入数据库(用于单元测试)
$task = Task::factory()->make();
// 创建时覆盖特定字段
$task = Task::factory()->create([
'title'   => '特定标题',
'user_id' => $user->id,
]);
// 创建带关联的数据
$user = User::factory()
->has(Task::factory()->count(5)->pending(), 'tasks')
->create();
// 等价于:
$user = User::factory()->hasTasks(5, ['status' => 'pending'])->create();
// 多对多关联
$task = Task::factory()
->hasAttached(Tag::factory()->count(3), [], 'tags')
->create();

UserFactory 完整示例

// database/factories/UserFactory.php
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name'              => $this->faker->name(),
'email'             => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password'          => bcrypt('password'),   // 测试统一用 'password'
'remember_token'    => Str::random(10),
'avatar'            => $this->faker->imageUrl(100, 100, 'people'),
'subscription_plan' => 'free',
];
}
public function unverified(): static
{
return $this->state(['email_verified_at' => null]);
}
public function premium(): static
{
return $this->state(['subscription_plan' => 'premium']);
}
public function admin(): static
{
return $this->afterCreating(function (User $user) {
$user->assignRole('admin');  // Spatie Permission
});
}
}

下一节Seeder 的环境隔离与幂等性——Seeder 用于填充初始数据(如角色、权限、分类),关键是要保证幂等性(重复执行不会出错),并在不同环境运行不同的数据集。