zoukankan      html  css  js  c++  java
  • Laravel factory 使用指引

    如果你想为你的 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 IlluminateSupportStr;
    use FakerGenerator as Faker;
    
    $factory->define(AppProduct::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 IlluminateSupportStr;
    use FakerGenerator as Faker;
    
    $factory->define(AppProduct::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(AppProduct::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(AppProduct::class)->make(['out_of_stock' => true]);

     

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

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

     

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

     

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

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

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

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

     

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

     

    制作多个模型

     

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

     

    $products = factory(AppProduct::class, 10)->make();

     

    工厂中的关系

     

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

     

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

     

    php artisan make:factory CategoryFactory --model=Category

     

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

     

    use IlluminateSupportStr;
    use FakerGenerator as Faker;
    
    $factory->define(AppCategory::class, function (Faker $faker) {
        return [
            'title' => $faker->word,
            'description' => $faker->realText(100)
        ];
    });

     

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

    如何通过 Factories 创建多条数据

     

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

     

    $products = factory(AppProduct::class, 10)->make();

     

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

     

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

     

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

     

    php artisan make:factory CategoryFactory --model=Category

     

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

     

    use IlluminateSupportStr;
    use FakerGenerator as Faker;
    
    $factory->define(AppCategory::class, function (Faker $faker) {
        return [
            'title' => $faker->word,
            'description' => $faker->realText(100)
        ];
    });

     

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

     

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

     

    use IlluminateSupportStr;
    use FakerGenerator as Faker;
    
    $factory->define(AppProduct::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(AppCategory::class)
        ];
    });

     

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

     

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

     

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

     

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

     

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

     

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

     

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

    更多学习内容请访问:

    腾讯T3-T4标准精品PHP架构师教程目录大全,只要你看完保证薪资上升一个台阶(持续更新)​zhuanlan.zhihu.com图标

  • 相关阅读:
    【转】C++轻量级可配置语法分析器
    [转载]正则表达式大全
    Batch update returned unexpected row count from update 错误解决方法
    [转载]C# ToString格式字符串整理(Format)(数字、日期和枚举的标准格式设置说明符)(SamWang)
    Centos配置mono环境
    ASP.NET MVC 4 简介
    添加控制器 Adding a Controller
    [转载]OrmHate
    [转载]张小龙谈移动互联网产品
    [转载]Golden Ratio in logo designs
  • 原文地址:https://www.cnblogs.com/a609251438/p/12796765.html
Copyright © 2011-2022 走看看