라라벨 model factory 사용하기(5 ~ 7 버전)

Laravel 8 은 라라벨 8 model factory 로 test data 만들기 을 참고하세요.

PHP Faker 프로젝트는 중단되었고 이를 fork 한 FakerPHP 프로젝트가 시작되었습니다.


라라벨 모델 팩토리는 5.1 에 추가된 기능으로 의미 있는 테스트 데이타를 만들어 주는 PHP 라이브러리인 Faker 를 프레임워크에 연동하여 손쉽게 DB seeding 및 테스트 데이타를 만들수 있는 기능입니다.


팩토리 지정

database/factories/ModelFactory.php 에 다음과 같이 생성할 데이타의 타입에 맞는 $faker의 formatters(name, email 등) 를 지정하면 됩니다.

database/factories/ModelFactory.php
$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => str_random(10),
        'remember_token' => str_random(10),
    ];
}); 
  • name : 랜덤한 이름을 생성합니다.
  • email: 랜덤한 이메일 문자열을 생성합니다.
$factory->define(App\User::class) 대신 아래처럼 사용할 수도 있습니다
$factory->define('App\User', function (Faker\Generator $faker) {


faker 사용

factory 헬퍼 함수를 사용하여 faker 를 호출할 수 있으며 간단하게 tinker 에서 테스트 할 수 있습니다.

데이타 생성

make() 를 사용하면 테스트 데이타를 만들 수 있습니다.

$  php artisan  tinker 
 
>>> factory(App\User::class)->make();
=> <App\User #00000000439a1a48000000001f1fd334> {
       name: "Darrin Farrell",
       email: "Seth37@Schaden.com"
   }


갯수 지정

factory 메소드의 두 번째 파라미터로 생성할 데이타의 갯수를 지정할 수 있습니다.

>>> factory(App\User::class, 3)->make(); 

=> <Illuminate\Database\Eloquent\Collection #00000000439a1a23000000001f1fd334> [
       <App\User #00000000439a1a3c000000001f1fd334> {
           name: "Jeremie VonRueden",
           email: "jHeathcote@Bailey.info"
       },
       <App\User #00000000439a1a3d000000001f1fd334> {
           name: "Bailee Hickle V",
           email: "oRunolfsson@yahoo.com"
       },
       <App\User #00000000439a1a3e000000001f1fd334> {
           name: "Electa Adams",
           email: "Dora72@Schmeler.com"
       }
   ]


Persistence 데이타 생성

make() 는 임시 데이타만 생성하므로 DB 에 입력하려면 create() 를 사용하면 됩니다.

>>> factory(App\User::class, 2)->create(); 
=> <Illuminate\Database\Eloquent\Collection #000000000a842bf70000000058ad9d8e> [
       <App\User #000000000a842bf60000000058ad9d8e> {
           name: "Evangeline McCullough",
           email: "Rylee.Cartwright@Collins.com",
           updated_at: "2015-06-11 23:59:29",
           created_at: "2015-06-11 23:59:29",
           id: 304
       },
       <App\User #000000000a842bf50000000058ad9d8e> {
           name: "Gracie White",
           email: "Joanie96@gmail.com",
           updated_at: "2015-06-11 23:59:29",
           created_at: "2015-06-11 23:59:29",
           id: 305
       }
   ]

생성된 데이타는 DBMS 에서 직접 확인할 수 있습니다.


만약 다음과 같은 에러가 발생한다면 factory 에 클래스가 제대로 설정되지 않은 것입니다.


InvalidArgumentException with message 'Unable to locate factory with name [default].'


Reference factory 사용

저자와 책의 정보를 담고 있는 Author와 Book 모델이 있을 경우 Model 의 관계는 다음과 같이 Author 는 여러 개의 Book 을 가질 수 있습니다.

Model

$ php artisan make:model Author
$ php artisan make:model Book
 Click here to expand...
Author.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Author extends Model
{
    //
    public function books()
    {
        return $this->hasMany(App\Book::class);
    }
}
Book.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    //
    public function author()
    {
        return $this->belongsTo(App\Author::class);
    }
}



migration

$ php artisan make:migration create_author_table --create=authors
$ php artisan make:migration create_book_table --create=books
 Click here to expand...
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateAuthorTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        //
        Schema::create('authors', function (Blueprint $table) {
             $table->increments('id');
             $table->string('name', 50);
             $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
       if (Schema::hasTable('authors')) {
            Schema::drop('authors');
        }
    }
}
 <?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateBookTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
             $table->increments('id');
             $table->integer('author_id')->unsigned();
             $table->foreign('author_id')->references('id')->on('authors');
             $table->string('name', 100);    
              $table->string('description', 200); 
             $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        if (Schema::hasTable('books')) {
            Schema::drop('books');
        }
    }
}

Book 모델 팩토리를 생성할 경우 참조되는 Author 정보가 있어야 합니다. 참조하는 모델 팩토리에서는 다음과 같이 $factory->create() 구문으로 참조되는 모델의 primary key 를 만들 수 있습니다.

$factory->define(App\Author::class, function ($faker) {
    return [
        'name' => $faker->name,
        'created_at' => $faker->dateTimeBetween($startDate = '-2 years', $endDate = '-1 years'),
        'updated_at' => $faker->dateTimeBetween($startDate = '-1 years', $endDate = 'now'),
    ];
});
 
$factory->define(App\Book::class, function ($faker) use($factory){
    return [
        'name' => $faker->text,
		// Author 모델 생성 및 id 리턴
        'author_id' =>  $factory->create('App\Author')->id,
        'created_at' => $faker->dateTimeBetween($startDate = '-2 years', $endDate = '-1 years'),
        'updated_at' => $faker->dateTimeBetween($startDate = '-1 years', $endDate = 'now'),
    ];
});
$factory->create()를 사용했으므로 Author 모델은 DB 에 insert 됩니다.
$ php artisan tinker
 
>>> factory(App\Book::class)->make()
=> <App\Book #000000005ff9c128000000001ef5e2b6> {
       name: "Harum laboriosam culpa consequatur nisi.",
       author_id: 1,
       created_at: "2014-05-30 13:03:26",
       updated_at: "2014-11-05 20:50:10"
   }


또는 아래와 같이 부모 테이블의 id 의 최소/최대 값을 얻어와서 numberBetween($min, $max) 메소드로 구간내의 임의의 값을 설정할 수 있습니다.

$factory->define(App\Book::class, function ($faker) use($factory){
	// 최대값과 최소값 가져오기
	$max = App\Author::max('id');
    $min = App\Author::min('id');
    return [
        'name' => $faker->text,
		// Author id 참조
        'author_id' =>  $faker->numberBetween($min, $max),
        'created_at' => $faker->dateTimeBetween($startDate = '-2 years', $endDate = '-1 years'),
        'updated_at' => $faker->dateTimeBetween($startDate = '-1 years', $endDate = 'now'),
    ];
});

state 사용

state 를 사용하면 Model 의 특별한 상태를 지정해서 생성할 수 있습니다. 만약 Book 모델에 베스트 셀러 여부를 나타내는 is_bestseller 와 주목받는 책임을 나타내는 is_featured 필드가 있다고 가정해 봅시다.


그리고 Model Factory 에 다음과 같이 Model state 를 정의합니다.

$factory->state(App\Book::class, 'bestseller', function() {
    return [
        'is_bestseller' => true,
    ];
});
 
$factory->state(App\Book::class, 'featured', function() {
    return [
        'is_featured' => true,
    ];
});


이제 best seller 인 책을 등록할 경우 아래와 같이 실행하면 is_bestseller 가 true 로 설정됩니다.

factory(\App\Book::class)->states('bestseller')->create();


주목받는 책은 featured state 를 지정해 주면 됩니다.

factory(\App\Book::class)->states('featured')->create();

 동시에 여러 개의 state 를 지정할 경우 파라미터로 구분해서 사용하면 되고 아래는 베스트셀러와 주목받는 책 2가지 state 를 설정합니다.

factory(\App\Book::class)->states('bestseller', 'featured')->create();


factory 에 parameter 전달


factory 생성시 외부에서 필요한 파라미터를 생성해서 전달할 수 있습니다.  예로 책을 등록할 때 저자 정보를 밖에서 설정해서 입력하고 싶을수 있습니다.

이럴 경우 createmake 메서드에 배열로 전달하려는 파라미터를 key, value 형식으로 입력하면 됩니다.

예로 다음 코드는 author_id => 1 이라는 파라미터를 전달합니다.

factory(\App\Book::class)->create(['author_id' -> 1]);


사용하는 factory 에서는 다음과 같이 클로저에 array 형식의 parameter 를 기술하고 array 의 값을 확인해서 사용하면 됩니다.

factory parameter
$factory->define(\App\Book::class, function (Faker $faker, array $param) {
   
    return [
		// 저자 정보 외부 입력값 사용
        'author_id' => $param['author_id'] ?? null,
        'title' => $faker->realText(20),
        'comment' => $faker->realText,
    ];
});


Database:Seeding 에서 사용

artisan db:seed 명령어에서도 factory 를 사용할 수 있습니다.

먼저 seed 클래스를 생성합니다.

$ php artisan make:seeder AuthorTableSeeder
Seeder created successfully.


database/seeds/AuthorTableSeeder.php  파일을 수정합니다.

<?php
use Illuminate\Database\Seeder;
class AuthorTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Author::class, 50)->create()->each(function ($a) {
            $a->books()->save(factory(App\Book::class)->make());
        });
    }
}


seed 를 적용합니다.

$ php artisan db:seed


PHPUnit 이나 Controller에서 사용

tinker 에서처럼 factory 헬퍼를 사용하면 됩니다.

class ExampleTest extends TestCase
{
    public function testFactoryExample()
    {        
		// 10 유저 생성 및 DB 입력
		 $users = factory('App\User', 10)->create();
         $this->assertEquals(10, count($users));
		 dump($users);
    }
}


Data Formatters

Faker 에는 다양한 데이타 형식을 생성하기 위한 Formatter 이 제공됩니다.

자주 쓰는 포맷터

$factory->define(App\Data::class, function ($faker) {
    return [
		// 1000 에서 1000000000 숫자 생성
        'num' => $faker->numberBetween($min=1000, $max=1000000000),
		// 2년전 ~ 1년 사이 datetime  생성 
        'date_before' => $faker->dateTimeBetween($startDate = '-2 years', $endDate = '-1 years'),
		// 1년전 ~ 현재 datetime  생성 
        'date_after' => $faker->dateTimeBetween($startDate = '-1 years', $endDate = 'now'),
		// 80자의 랜덤 문자열 생성
        'text' => $faker->text(80),
		// 의미있는 User Agent 정보 생성
		'ua' => $faker->userAgent,
    ];
}); 

Image 

랜덤한 이미지를 만들 수 있는 사이트인 http://lorempixel.com/ 를 통해 다양한 종류의 이미지를 만들어 낼 수 있습니다.

imageUrl($width = 640, $height = 480) // 'http://lorempixel.com/640/480/'
imageUrl($width, $height, 'cats')     // 'http://lorempixel.com/800/600/cats/'
imageUrl($width, $height, 'cats', true, 'Faker') // 'http://lorempixel.com/800/400/cats/Faker'
image($dir = '/tmp', $width = 640, $height = 480) // '/tmp/13b73edae8443990be1aa8f1a483bc27.jpg'
image($dir, $width, $height, 'cats')  // 'tmp/13b73edae8443990be1aa8f1a483bc27.jpg' it's a cat!
image($dir, $width, $height, 'cats', true, 'Faker') // 'tmp/13b73edae8443990be1aa8f1a483bc27.jpg' it's a cat with Faker text


다음은 다양한 카테고리의 이미지를 'Faker'라는 단어를 포함하여 생성하고 라라벨의 storage 폴더에 저장하는 예제입니다. mimeType 은 예제로 넣은 거라 실제 이미지의 mimeType 과는 관계가 없습니다.

$factory->define(App\FileEntry::class, function (Faker\Generator $faker) {
	$category = ['abstract', 'animals', 'business', 'cats', 'city', 'food',
		'nightlife', 'fashion', 'people', 'nature', 'sports', 'technics', 'transport'];

	$fn = $faker->image($dir = storage_path(), $width = 640, $height = 480,
		$category[rand(0, count($category) -1)], true, 'Faker');
    return [
        'filename' => basename($fn),
        'mime' => $faker->mimeType,
    ];
});


기본 locale 설정하기

Laravel Factory 기본 locale 변경해서 사용하기 참고


tinker 에서는 다음과 같이 실행하면 됩니다.

>>> factory('App\User')->make();
=> <App\User #00000000585b6fbe00000000214b92fc> {
       name: "양아린",
       company: "동하식품",
       address: "인천광역시 동구 강남대로 9492",
       phoneNumber: "070-1234-5678",
       email: "k@.com",
       internet: "kr"
   }


realText 문제 해결

랜덤한 문자열을 생성하는 realText 를 사용하면 가끔 한글이 깨집니다. 이 문제는 https://github.com/fzaninotto/Faker/pull/1380 에서 해결됐지만 아직 정식 릴리스가 안 되어 있으므로 한글 realText 가 필요할 경우 develop 버전을 사용해야 합니다. (https://github.com/fzaninotto/Faker/issues/1391)

composer.json
{
   "require": {
      "fzaninotto/faker": "dev-master as 1.7.1",
   }
}


같이 보기


Ref