深入了解工厂模式:工厂状态

Laravel

我猜想,如果您熟悉 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 附带的用户工厂。 工厂定义没有任何我想批评的地方——它完全没问题。 但是,我希望您考虑到,当您运行测试套件时,每次都会生成新值:

// 第一次运行
array:5 [
  "name" => "Troy Miller III"
  "email" => "orrin93@example.org"
  "updated_at" => "2018-04-09 01:35:38"
  "created_at" => "2018-04-09 01:35:38"
  "id" => 1
]
// 第二次运行
array:5 [
  "name" => "Mr. Zachariah McGlynn"
  "email" => "lturcotte@example.net"
  "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' => 'example@example.com',
        // ...
    ];
});

硬编码的名称和邮箱是个微妙的改变,但现在我的测试可能看起来像这样:

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');
}

您应该在您的程序中做您认为正确的事情,但我希望您至少考虑一下 您不必对所有事情都使用工厂。

如果您不想失去工厂提供的随机性,请考虑使用工厂状态进行包含一些静态值的默认测试:

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

然后在你的测试中,你需要一个基本的静态用户:

public function testWelcomeMessageTest()
{
    factory('App\User')->states('user')->create();
    $response = $this->get('/');
    $response->assertSeeText('Welcome Example 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 的领域。

即使存在这些缺点,我们仍然有一种非常好的声明性方法来对 post 使用用户状态。

其他工厂模型状态方法

我们也可以在测试框架中使用 Trait 来为使用 posts 的用户创建一个更加动态的状态,以及我们希望用户使用的任何其他状态:

<?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。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://laravel-news.com/going-deeper-wi...

译文地址:https://learnku.com/laravel/t/62386

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!