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 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.com圖標

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM