欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Laravel factory 使用指引

程序员文章站 2022-04-15 22:54:34
如果你想为你的 Laravel 项目写一些测试,那么你可能需要在某个时候编写一些工厂模式。 当我第一次听到工厂一词时,我不知道它的含义和作用,更不用说了解它们可以为你的测试带来的好处了。 假设你有一个产品 Controller,该控制器具有一种存储方法来保存新产品的详细信息。 产品可能具有产品代码, ......

如果你想为你的 laravel 项目写一些测试,那么你可能需要在某个时候编写一些工厂模式。 当我第一次听到工厂一词时,我不知道它的含义和作用,更不用说了解它们可以为你的测试带来的好处了。

假设你有一个产品 controller,该控制器具有一种存储方法来保存新产品的详细信息。 产品可能具有产品代码,标题,价格,描述和标签等属性,这些都在请求中发送到 store 方法。

 

如果你想测试这个 endpoint,可以创建一个属性数组,然后在 post 请求中发送它

$product = [
    'product_code' => 'abc123',  
    'title' => 'my amazing product', 
    'price' => 100, 
    'description' => 'this product will change the way you wash your dishes forever',
    'tagline' => 'voted best in category'
];

$response = $this->post(route('products.store'), $product);

// 你的断言
$response->assertsuccessful();

这么做没问题。

 

但是如果你想在另一个测试中使用该 product,比如测试更新 product,你不得不在下一个测试方法中复制该数组, 或者可以将其提取到测试的 setup () 方法中 并使其成为 $this->product 以重复利用。

 

如果你还有另一个测试类要测试将 product 添加到 category 中,那你该怎么办?怎样才能重用你的产品代码?你会如何定义不同模型之间的关系? 幸运的是,工厂模式可以解决这些问题。

creating a factory

你可以通过创建相应的工厂方法来产生你所需要的数据。 这里提供了一个 artisan 命令可以帮助你快速的、根据你的模型创建对应的工厂方法。

php artisan make:factory productfactory --model=product

执行上面命令后,你会在 database/factories 目录下看到一个以你的 product 模型为基础的,名为 productfactory.php 的文件,你可以通过自定义字段名,以及该字段需要的值来获取你需要的数据。下面是一个例子:

use illuminate\support\str;
use faker\generator as faker;

$factory->define(app\product::class, function (faker $faker) {
    return [
        'product_code' => 'abc123',
        'title' => 'my amazing product', 
        'price' => 100, 
        'description' => 'this product will change the way you wash your dishes forever',
        'tagline' => 'voted best in category'
    ];
});

使用 faker 定义假数据

我们可以使用先前定义的数组中的静态值,但是模型工厂允许我们使用 faker 生成一些假数据,这样每次生成新模型时测试数据都不一样。

所以对于商品,我们可以使用 numerify 之类的东西来生成不同的商品编号。 这将生成一个以 abc 开头的代码,后跟三位数字,以代替散列。

 

'product_code' => $faker->numerify('abc###')

 

如果我们需要要确保商品编码唯一,该怎么办? faker 有一个 unique 方法,可以确保生成的内容不在表中存在。

 

'product_code' => $faker->unique()->numerify('abc###')

对于标题,我们可以使用 words 方法来生成一些单词。 如果您想要一个单词数组,则只需要声明您想要多少个单词,但是当我们想要一些产品文本时,我们将 true 添加为第二个参数。

'title' => $faker->words(3, true)

对于价格,我们可以使用 randomnumber 方法,但是作为货币,我们可能希望保留数字小数点后两位,因此我们将使用 randomfloat 方法。 我们还需要限制最小值和最大值,因此我们可以将它们作为下两个参数传递。

 

'price' => $faker->randomfloat(2, 10, 100)

对于商品描述,我们可以再次使用 words 方法并将其长度设置为更大的值,也可以使用 paragraph,但是可以使用 realtext 获得一些看起来更逼真的文本。 我们需要设置所需字符的最大长度。 可以说,在我们的情况下,最多 200 个字符是可以的。

'description' => $faker->realtext(200)

最后,对于品牌,我们可以使用比单词或真实文本更有趣的东西,称为流行短语。

'tagline' => $faker->catchphrase

这是我们更新的 productfactory。

use illuminate\support\str;
use faker\generator as faker;

$factory->define(app\product::class, function (faker $faker) {
    return [
        'product_code' => $faker->unique()->numerify('abc###'),
        'title' => $faker->words(3, true), 
        'price' => $faker->randomfloat(2, 10, 100), 
        'description' => $faker->realtext(200),
        'tagline' => $faker->catchphrase
    ];
});

因此,现在我们有了模型工厂,可以通过模型工厂助手来更新测试以使用它。

$product = factory(\app\product::class)->make();
$response = $this->post(route('products.store'), $product->toarray());

$response->assertsuccessful();

在此示例中,有两点需要注意。

 

首先,我们使用 factory()->make() 而非 factory()->create()。它们可能听起来很相似,但是 make 将创建一个新的模型供你在测试中使用,而 create 将创建它并将其持久化到你的数据库中。如果 product 的代码中有唯一性验证,使用 create 可能会导致问题,即当数据库中已经有对应 product 时 create 操作会失败。

 

第二点需要注意的是 $this->post() 期望第二个参数是一个数组,因此我们必须在末尾使用 $product->toarray() 方法把它从对象转换成数组。

工厂状态

这似乎达到了我们的要求,但是现在我们想给 product 加一个标记表示它缺货了。将字段添加到数据库后,我们可以使用新字段更新 product 工厂。

'out_of_stock' => $faker->boolean()

这个方法会把缺货标记随机设置为 true 或 false。

 

但是,如果我们想创建一种总是缺货的 product 怎么办?一种方法是在测试中使用工厂助手时覆盖该值。

 

$product = factory(\app\product::class)->make(['out_of_stock' => true]);

 

如果我们想使代码更具可重用性,我们可以在工厂中创建一个状态。 state 方法将模型设置为第一个参数,将状态名称设置为第二个参数,将要覆盖的值设置为第三个参数。

$factory->state(\app\product::class, 'out_of_stock', [
    'out_of_stock' => true
]);

 

在我们的测试中,我们可以调用工厂,然后在调用 make () 之前应用状态。

 

$product = factory(\app\product::class)->states('out_of_stock')->make();

工厂模式真正强大的地方在于当我们在工厂中具有多个状态时,它们可以同时应用。 例如,如果我们有一个状态表示某个 product 是免费的,我们可以覆盖它的价格。

$factory->state(\app\product::class, 'free', [
    'price' => 0.00
]);

所以,当我们想要一个缺货且免费的 product 时,在测试中可以对它同时应用这两种状态。

 

$product = factory(\app\product::class)->states(['out_of_stock', 'free')->make();

 

制作多个模型

 

另一个小提示,假如我们想要 10 个 product 而不是 1 个,我们不需要调用 10 次工厂,只需要在工厂助手中加一个数量作为第二个参数,它就会自动为我们生成 10 个 product。

 

$products = factory(\app\product::class, 10)->make();

 

工厂中的关系

 

我们对 product 的测试进行得很顺利,但是现在我们有一个属于某个 category 下的产品。工厂模式允许我们使用另一个工厂来测试关系。

 

将 category 表添加到数据库中并定义 product 和 category 模型的关系后,我们就可以建立 category 工厂。

 

php artisan make:factory categoryfactory --model=category

 

为简单起见,category 只有标题和描述两个字段,因此我们可以用 word 生成标题,用 realtext 生成描述。

 

use illuminate\support\str;
use faker\generator as faker;

$factory->define(app\category::class, function (faker $faker) {
    return [
        'title' => $faker->word,
        'description' => $faker->realtext(100)
    ];
});

 

现在,我们可以将 category_id 添加到 product 工厂,但是如何将 product 和 category 关联起来?

如何通过 factories 创建多条数据

 

开发中,我们可能需要获取多条数据,这时候可以通过传递 factory 第二个参数来实现。下面是例子:

 

$products = factory(\app\product::class, 10)->make();

 

如何通过 factories 创建有关联的数据

 

现在产品数据的产生已经没什么问题了,但是现在我们想要获取某个分类下的数据,要怎么办呢?在 factories 中我们可以使用另一个工厂类来达到数据关联的效果。

 

为了获取某分类下的产品,我们需要一个 category 工厂类来创建相应的 category 数据。我们可以通过以下命令创建对应的工厂类:

 

php artisan make:factory categoryfactory --model=category

 

为了演示方便,我们的 category 只需要标题和描述两个字段,我们可以用 $faker->word 创建标题数据,用 $faker->realtext(100) 来创建描述数据。

 

use illuminate\support\str;
use faker\generator as faker;

$factory->define(app\category::class, function (faker $faker) {
    return [
        'title' => $faker->word,
        'description' => $faker->realtext(100)
    ];
});

 

现在我们已经通过 category 工厂类来产生分类数据,但是要如何将产品和分类数据关联呢?

 

我们可以使用刚在 product factory 中创建的工厂,而不是像其他字段那样使用 faker。

 

use illuminate\support\str;
use faker\generator as faker;

$factory->define(app\product::class, function (faker $faker) {
    return [
        'product_code' => $faker->unique()->numerify('abc###'),
        'title' => $faker->words(3, true), 
        'price' => $faker->randomfloat(2, 10, 100), 
        'description' => $faker->realtext(200),
        'tagline' => $faker->catchphrase,
        'out_of_stock' => $faker->boolean,
        'category_id' => factory(\app\category::class)
    ];
});

 

它知道它需要 id,因此你不需要使用 factory()->create() 或者 factory()->create()->id 获取类别 id。

 

现在,当你运行创建产品的测试时,它知道该产品应该属于某个类别并为你创建类别,而无需你在测试中做任何额外的操作。

 

如果对于特定的测试场景,你想创建属于某个类别的产品,则可以先定义类别,然后覆盖产品上的类别 id,使其成为你刚刚创建的类别。

 

$category = factory(\app\category::class)->create();

$products = factory(\app\product::class, 10)->create(['category_id' => $category->id]);

 

然后就可以断言该类别有 10 个产品

 

$this->assertequals(10, $category->fresh()->products->count());

 

希望本文能为你提供一些关于工厂测试的想法,帮你开始在测试中使用工厂,并使将来编写测试变得更加容易。

 

更多学习内容请访问:

腾讯t3-t4标准精品php架构师教程目录大全,只要你看完保证薪资上升一个台阶(持续更新)​zhuanlan.zhihu.comLaravel factory 使用指引