使用工厂状态深入探索工厂

发布于 作者

Going Deeper with Factories Through Factory States image

我猜想,如果你熟悉 Laravel,你可能会在应用程序开发中使用 模型工厂,甚至可能使用工厂状态。文档展示了使用工厂进行种子和创建测试数据的机制,但是我想探讨一下有效地使用工厂与模型交互的一些指导思想。

以下是我在更有效地使用工厂状态方面一直在思考的一些方法

首先,创建使用静态值的工厂,而不是为所有内容使用 Faker。

其次,你的工厂应该只创建创建模型实例所需的最小属性集。

使用静态数据代替 Faker

我并不是说使用 Faker 是 _错误的_,而是说静态值可以使测试数据比随机数据更清晰。

考虑以下 `User` 工厂

$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => str_random(10),
];
});

代码示例是 Laravel 5.6 附带的用户工厂。工厂定义中没有任何我想批评的内容 - 它完全没问题。但是,我想让你考虑一下,当你运行你的测试套件时,每次都会生成新的值

// First test run
array:5 [
"name" => "Troy Miller III"
"email" => "[email protected]"
"updated_at" => "2018-04-09 01:35:38"
"created_at" => "2018-04-09 01:35:38"
"id" => 1
]
 
// Second test run
array:5 [
"name" => "Mr. Zachariah McGlynn"
"email" => "[email protected]"
"updated_at" => "2018-04-09 01:35:41"
"created_at" => "2018-04-09 01:35:41"
"id" => 1
]

你可能认为这种测试数据的随机化是一件 _好事_,它使你的测试套件更健壮。这是一个有效的论点,但考虑一下你需要编写的验证数据的配套测试

public function testWelcomeMessageTest()
{
$user = factory('App\User')->create();
 
$response = $this->get('/');
$response->assertSeeText('Welcome '.$user->name);
}

我想再次强调,这个测试本身并没有什么错,但我感觉它涉及了额外的“魔法”,需要你动脑筋才能理解。我使用了一个变量来断言测试通过,而不是使用硬编码的断言,在我看来,这使测试更易读。

考虑以下工厂

$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => 'Example User,
'email' => '[email protected]',
// ...
];
});

硬编码的名称和电子邮件是一个细微的变化,但现在我的测试可能看起来像这样

public function testWelcomeMessageTest()
{
factory('App\User')->create();
 
$response = $this->get('/');
$response->assertSeeText('Welcome Example User');
}

另一种方法是使用具有动态数据的工厂,并覆盖你想要测试的内容

public function testWelcomeMessageTest()
{
factory('App\User')->create([
'name' => 'Example User',
]);
 
$response = $this->get('/');
$response->assertSeeText('Welcome Example User');
}

你应该在你的应用程序中做你觉得合适的事情,但我希望你至少考虑一下 _你并不需要为所有内容都使用 Faker_。事实上,通过这种细微的转变,你可能会在测试中获得更清晰的思路。

如果你不想失去 Faker 提供的随机性,可以考虑为你的默认测试使用工厂状态,其中包含一些静态值

$factory->define(App\User::class, 'user', function (Faker $faker) {
return [
'name' => 'Example User,
'email' => '[email protected]',
];
});

然后在你的测试中,如果你想要一个基本的静态用户

public function testWelcomeMessageTest()
{
factory('App\User')->states('user')->create();
 
$response = $this->get('/');
$response->assertSeeText('Welcome Example User');
}

`user` 状态代表你的模型所需的属性基本集 - 它是创建模型所需的最小属性集。

最小可行属性

创建具有创建模型所需最小数据量的工厂和测试数据是你在测试中应该培养的一个好习惯。如果一个字段是可空的,或者模型具有可选的关系,不要在默认工厂中定义它们。

你的测试将更容易帮助你发现空值和空关系的问题。空模型关系通常是用户在你的应用程序中遇到的第一个状态。

当数据库中的一个列可以为空时,默认情况下在你的工厂中省略它,然后使用状态来进一步测试具有超过最小数据要求的模型。

以论坛应用程序为例。当用户首次注册时,他们还没有创建任何帖子,也没有对其他人的帖子进行任何评论。这代表了你应用程序中最基本的用户,具有最低要求。

拥有工厂数据和 Faker 在你的处置范围内,使你很想尽可能多地填写数据,但只在你默认的模型工厂状态中定义绝对必要的數據。

使用状态增强工厂数据

以论坛应用程序为例,我们可以为具有帖子的用户定义一个状态,在我们的测试需要超过最低要求数据的情况下

$factory
->state(App\User::class, 'with_posts', [])
->afterCreatingState(App\User::class, 'with_posts', function ($user, $faker) {
factory(App\Post::class, 5)->create([
'user_id' => $user->id,
]);
});

此状态没有任何覆盖数据,因此我们可以传递一个空数组作为第三个参数,而不是闭包。我们使用 `afterCreatingState` 方法来定义与用户相关联的一些帖子,现在我们可以用它们来进行测试

$user = factory('App\User')->states('with_posts')->create();

这种方法的最大缺点是 `afterCreatingState` 回调中硬编码的 `5` 个帖子。我只是在写伪代码,但类似这样的 API 可能会很好

$user = factory('App\User')
->states('with_posts', ['posts_count' => 5])
->create();

然后在工厂 API 的另一端,类似这样的内容

$factory
->state(App\User::class, 'with_posts', [])
->afterCreatingState(App\User::class, 'with_posts', function ($user, $faker, $config) {
factory(App\Post::class, $config->get('posts_count', 5))->create([
'user_id' => $user->id,
]);
});

再次强调,_这是伪代码,不能正常工作!_但我想要展示几种使这种方法更好的方式。也许这是一个包可以扩展基本工厂功能的领域,以实现这种 API。

即使存在缺点,我们仍然拥有一个很好的声明式方法来使用具有帖子的用户状态。

其他工厂状态方法

我们还可以使用测试框架中的 trait 来为具有帖子的用户创建更动态的状态,以及我们想要添加到用户中的任何其他状态

<?php
 
namespace Tests\factories;
 
trait UserFactory
{
public function userWithPosts($config = [])
{
$user = factory(\App\User::class)->create();
$posts = factory(
\App\Post::class,
$config['posts_count'] ?? 5
)->make([
'user_id' => $user->id,
]);
 
$user->posts()->saveMany($posts);
 
return $user;
}
}

在你的测试中,如果你需要一个具有帖子的用户,你可以使用以下代码

namespace Tests\Feature;
 
use Tests\TestCase;
use Tests\factories;
use Illuminate\Foundation\Testing\RefreshDatabase;
 
class ExampleTest extends TestCase
{
use RefreshDatabase;
use factories\UserFactory;
 
public function testWelcomeMessageTest()
{
$user = $this->userWithPosts(['posts_count' => 5]);
// ...
}
}

我喜欢在顶部定义 `use Tests\factories`,以及 `use factories\UserFactory;` 来导入 trait,因为我觉得这样一眼就能看出 trait 是用于工厂数据的。使用 trait 需要相当多的样板代码,但比具有硬编码为 5 个帖子数量的静态工厂状态更灵活。

了解更多

你可以在 Laravel 文档中了解有关工厂状态的更多信息。如果你想更多地了解工厂的工作原理,另一个很好的来源是一个 Ruby gem Factory Bot,特别是它的 入门文档

工厂应该是最基本的 是另一个关于使用工厂的优秀资源,由上面提到的 Factory Bot 库的作者编写。

Paul Redmond photo

Laravel 新闻的撰稿人。全栈 Web 开发人员和作者。

Cube

Laravel 新闻通讯

加入 40,000 多名其他开发者,绝不错过新的技巧、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动补全和对本地和生产环境的即时反馈进行微调。

Tinkerwell
No Compromises logo

绝不妥协

来自 No Compromises 播客的两位经验丰富的开发人员 Joel 和 Aaron 现已开放接单,为您的 Laravel 项目提供服务。 ⬧ 固定月薪 $7500。 ⬧ 无冗长的销售流程。 ⬧ 无需签订合同。 ⬧ 100% 退款保证。

绝不妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

还在使用旧版本的 Laravel 吗? 立即自动升级 Laravel 并对代码进行现代化改造,保持应用程序的鲜活。

Shift
Bacancy logo

Bacancy

每月只需 $2500,即可聘请经验丰富的 Laravel 开发人员(拥有 4-6 年经验)为您的项目提供助力。 获取 160 小时的专业知识和 15 天免费试用。 立即安排电话咨询!

Bacancy
Lucky Media logo

Lucky Media

现在就选择 Lucky - Laravel 开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

Laravel 的电子商务。 一个开源软件包,将现代无头电子商务功能的强大功能带到 Laravel。

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

官方 Laravel 招聘网站

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit 是一个 Laravel SaaS Starter Kit,包含运行现代 SaaS 所需的所有功能。 支付、精美结账、管理面板、用户仪表盘、身份验证、即用型组件、统计信息、博客、文档等等。

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

您的无缝 Laravel 升级合作伙伴,降低成本,加速创新,帮助企业取得成功

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。 支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。 探索灵活、现代的 MongoDB 数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

在您的 Laravel 应用程序中添加 Swagger UI

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章