데이터베이스 테스팅

시작하기

라라벨은 데이터베이스를 기반으로 하는 애플리케이션을 손쉽게 테스트 할수 있도록 도와주는 다양한 툴을 제공합니다. 우선 assertDatabaseHas 헬퍼를 이용하여 데이터베이스 안에 특정 조건을 만족하는 데이터가 존재하는지 확인할 수 있습니다. 예를 들어, users 테이블에 [email protected]email 값을 가진 레코드가 존재하는지 확인하기 위해 다음과 같이 할 수 있습니다:

public function testDatabase()
{
    // Make call to application...

    $this->assertDatabaseHas('users', [
        'email' => '[email protected]'
    ]);
}

또한 데이터베이스에 데이터가 존재하지 않는 것을 확인하기 위해서 assertDatabaseMissing 헬퍼 함수를 사용할 수 있습니다.

assertDatabaseHas 메소드와 기타 다른 헬퍼들은 사용하기에 더 편리합니다. PHPUnit의 테스트 구문에서 자유롭게 이 함수들을 사용할 수 있습니다.

팩토리 생성하기

팩토리 클래스를 생성하기 위해서는 make:factory 아티즌 명령어를 사용합니다:

php artisan make:factory PostFactory

새로운 팩토리는 database/factories 디렉토리에 생성됩니다.

팩토리에 의해서 생성될 모델의 이름을 지정하고자 한다면 --model 옵션을 사용하면 됩니다. 이 옵션은 생성된 팩토리 파일을 주어진 모델로 미리 채워둡니다:

php artisan make:factory PostFactory --model=Post

각각의 테스트 수행 후에 데이터베이스 재설정하기

종종 이전의 테스트를 위한 데이터가 다음에 이어지는 테스트들을 방해하는 것을 막기 위해 각각의 테스트가 끝난 뒤에 데이터베이스를 재설정하는 것이 유용합니다. RefreshDatabase 트레이트-trait 은 전통적인 데이터베이스 또는 in-memory 데이터베이스를 사용하는 경우 여러분의 테스트 데이터베이스를 마이그레이션 하는데 가장 적합한 방법입니다. 테스트 클래스에 트레이트-trait를 사용하면 손쉽게 처리됩니다:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

트랜잭션 이용하기

데이터베이스를 재설정하는 다른 방법으로는 데이터베이스 트랜잭션으로 모든 테스트 케이스를 감싸는 것입니다. 이 때에도 라라벨은 편리하게 DatabaseTransactions 트레이트-trait을 제공하여 이것이 자동으로 처리되도록 합니다:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseTransactions;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

팩토리 작성하기

테스트를 할 때, 실행하기 전 데이터베이스에 몇몇 레코드를 입력하는 것이 필요할 수 있습니다. 이 테스트 데이터를 생성할 때 수동으로 각각의 컬럼의 값을 지정하는 대신에 라라벨은 모델 팩토리를 사용하여 각각의 Eloquent 모델을 위한 기본 속성의 세트를 정의하도록 해줍니다. 먼저 애플리케이션의 database/factories/UserFactory.php 파일을 살펴보겠습니다. 이 파일은 바로 사용이 가능한 하나의 팩토리 정의를 가지고 있습니다.

use Faker\Generator as Faker;

$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),
    ];
});

팩토리의 정의를 제공하는 클로저 안에서 모든 모델의 속성의 기본 테스트 값을 반환할 수 있습니다. 클로저는 Faker PHP 라이브러리 인스턴스를 전달 받을 것이고, 이는 테스트할 수 있는 다양한 랜덤 데이터를 편리하게 생성할 수 있게 해줍니다.

여러분은 또한 보다 나은 구성을 위해서 각각의 모델들을 위한 추가적인 팩토리 파일들을 생성할 수도 있습니다. 예를 들어 database/factories 디렉토리에 UserFactory.phpCommentFactory.php 파일을 생성할 수 있습니다. factories 디렉토리 안에 들어 있는 모든 파일들은 라라벨에 의해서 자동으로 로딩됩니다.

팩토리 상태(States)

상태를 통하면, 모델 팩토리에서 적용 할 수 있는 개별 수정사항을 정의 할 수 있습니다. 예를 들어, User 모델이 기본 속성 값 중에 하나를 변경하는 delinquent 상태를 가지고 있다고 가정해보겠습니다. 여러분은 state 메소드를 사용하여 상태 변경을 정의할 수 있습니다. 간단한 상태를 정의한다면, 속성값들의 변경사항을 배열로 전달 할 수 있습니다:

$factory->state(App\User::class, 'delinquent', [
    'account_status' => 'delinquent',
]);

상태에 $faker 인스턴스가 필요한 경우라면, Closure를 사용하여 상태의 속성에 대한 변경사항을 지정할 수 있습니다:

$factory->state(App\User::class, 'address', function ($faker) {
    return [
        'address' => $faker->address,
    ];
});

팩토리 콜백

팩토리 콜백은 afterMakingafterCreating 메소드를 사용하여 등록합니다. 이는 모델을 생성할 때나 생성된 다음에 추가적인 작업을 수행할 수 있게 해줍니다. 예를 들어 콜백을 사용하여 생성된 모델과, 추가 모델을 서로 연결할 수 있습니다:

$factory->afterMaking(App\User::class, function ($user, $faker) {
    // ...
});

$factory->afterCreating(App\User::class, function ($user, $faker) {
    $user->accounts()->save(factory(App\Account::class)->make());
});

또한 팩토리 상태에 대한 콜백을 정의할 수도 있습니다:

$factory->afterMakingState(App\User::class, 'delinquent', function ($user, $faker) {
    // ...
});

$factory->afterCreatingState(App\User::class, 'delinquent', function ($user, $faker) {
    // ...
});

팩토리 사용하기

모델 생성하기

팩토리를 정의한 뒤에는, 테스트파일 에서나 모델 인스턴스를 생성하기 위한 시드 파일안에서 글로벌 factory 함수를 사용할 수 있습니다. 그럼 모델들을 생성하는 몇몇의 예제를 살펴보겠습니다. 우선 모델을 생성하지만 데이터베이스에 저장하지는 않는, make 메소드를 사용할 것입니다:

public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // Use model in tests...
}

또한 여러 모델의 컬렉션을 생성하거나 주어진 타입의 모델을 생성할 수도 있습니다:

// Create three App\User instances...
$users = factory(App\User::class, 3)->make();

상태(States) 적용하기

어떤 모델이라도 팩토리 상태(states)를 적용할 수 있습니다. 모델에 여러개의 팩토리 상태(state) 변경을 지정하고자 한다면, 다음과 같이 각각의 팩토리 상태(state)들의 이름을 지정하면 됩니다:

$users = factory(App\User::class, 5)->states('delinquent')->make();

$users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();

속성 재지정(오버라이딩) 하기

모델의 몇몇 기본값들을 대체하고 싶다면 make 메소드로 값들의 배열을 전달하면 됩니다. 지정된 값들만 대체되고 나머지 값들은 팩토리에 의해 지정된 기본값들을 유지할 것입니다.

$user = factory(App\User::class)->make([
    'name' => 'Abigail',
]);

모델 저장하기

create 메소드는 모델 인스턴스를 단지 생성하기만 하기 때문에 데이터베이스에 저장하려면, Eloquent의 save 메소드를 사용하면 됩니다:

public function testDatabase()
{
    // Create a single App\User instance...
    $user = factory(App\User::class)->create();

    // Create three App\User instances...
    $users = factory(App\User::class, 3)->create();

    // Use model in tests...
}

create 메소드에 배열을 전달하여 모델의 속성들을 재지정 할 수 있습니다:

$user = factory(App\User::class)->create([
    'name' => 'Abigail',
]);

관계(relationships)

이 예제에서는, 생성된 몇몇의 모델에 관계를 추가할 것입니다. 여러 모델을 생성하기 위해서 create 메소드를 사용할 때 Eloquent 컬렉션 인스턴스가 반환되어 each와 같이 컬렉션이 제공하는 편리한 함수들을 이용할 수 있도록 해줍니다:

$users = factory(App\User::class, 3)
           ->create()
           ->each(function ($u) {
                $u->posts()->save(factory(App\Post::class)->make());
            });

관계 & 속성 클로저-Closures

팩토리 정의안에서 클로저를 속성으로 사용하여 모델에 관계를 추가할 수도 있습니다. 예를 들어 Post 가 생성될 때 User 인스턴스를 생성하고자 한다면, 다음처럼 하면 됩니다:

$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        }
    ];
});

이 클로저는 그것들을 정의하는 팩토리의 계산된 속성 배열을 받을 수도 있습니다:

$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        },
        'user_type' => function (array $post) {
            return App\User::find($post['user_id'])->type;
        }
    ];
});

사용 가능한 Assertions

라라벨은 PHPUnit 테스트에서 사용가능한 데이터데이스 assertion을 제공합니다:

메소드 설명
$this->assertDatabaseHas($table, array $data); 데이터베이스에서 테이블에 주어진 데이터가 존재하는지 확인.
$this->assertDatabaseMissing($table, array $data); 데이터베이스에서 테이블에 주어진 데이터가 존재하지 않는 것을 확인.
$this->assertSoftDeleted($table, array $data); 주어진 레코드가 소프트 삭제 되었는지 확인.