테스팅
시작하기
라라벨은 단위 테스트(Unit Test)를 고려하여 구성되어 있습니다. 실제로는 PHPUnit을 통한 테스팅이 별다른 설정 없이도 지원되며 애플리케이션을 위한 phpunit.xml
파일이 이미 설정되어 있습니다. 또한 이 프레임워크는 다양한 표현을 통해서 애플리케이션을 테스트할 수 있도록 편리한 헬퍼 메소드들을 제공합니다.
tests
디렉토리에는 테스트 예제 파일이 제공되어 있습니다. 새롭게 라라벨 애플리케이션을 설치한 후 커맨드 라인에서 그대로 phpunit
명령어를 실행하면 테스트를 수행할 수 있습니다.
테스트 환경
단위 테스트를 실행할 때 라라벨은 자동으로 설정 환경을 testing
에 구성해 놓습니다. 또한 라라벨은 테스트 환경에서의 session
과 cache
을 위한 설정 파일들을 포함하고 있습니다. 이 두개의 드라이버는 테스트 환경에서 array
로 설정되며 이는, 세션 또는 캐시 데이터가 테스팅이 진행되는 동안에만 존재한다는 것을 의미합니다.
여러분은 필요한 경우에 자유롭게 테스트 환경 설정을 생성할 수 있습니다. testing
환경 변수는 phpunit.xml
파일에 설정되어 있습니다.
테스트 정의 & 실행하기
새로운 테스트 케이스를 생성하려면 make:test
아티즌 명령어를 이용하십시오:
php artisan make:test UserTest
이 명령어는 tests
디렉토리에 새로운 UserTest
클래스를 만들 것입니다. 그리고 나서 일반적인 상황과 같이 PHPUnit을 이용하여 테스트 메소드를 정의하면 됩니다. 테스트를 실행하려면 간단하게 터미널에서 phpunit
커맨드를 실행하면 됩니다:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UserTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
}
}
주의: 테스트 클래스 내에서 여러분만의
setUp
메소드를 정의한다면 반드시parent::setUp
를 호출하십시오.
애플리케이션 테스트하기
라라벨은 애플리케이션에 HTTP request-요청을 하고, 결과을 검사하고, 또한 form을 채우는 매우 다양한 사용이 가능한 API를 제공합니다. 다음과 같이, tests
디렉토리의 ExampleTest.php
파일을 살펴보겠습니다:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5')
->dontSee('Rails');
}
}
visit
메소드는 애플리케이션에 GET
request-요청을 만듭니다. see
메소드는 주어진 텍스트가 애플리케이션에서 반환된 reponse-응답에 있는지 확인 합니다. dontSee
메소드는 주어진 텍스트가 애플리케이션 response-응답으로 반환되지 않았다는 것을 확인합니다. 이것은 라라벨이 제공하는 가장 기본적인 애플리케이션 테스트입니다.
애플리케이션과 상호작용하기
물론 단순하게 주어진 응답에 텍스트가 나타날지 확인하는 것 이상의 테스트가 가능합니다. 링크 클릭하는 것과 form을 채워넣는 예제들을 살펴보겠습니다:
링크 클릭하기
이 테스트에서는 애플리케이션에 요청을 보내고, 응답으로 돌아온 링크를 "클릭"하여, 특정 URI에 이동된것을 확인할 것입니다. 예를 들어 응답으로 "About Us"이라는 텍스트 값을 가진 링크가 왔다고 가정해봅시다:
<a href="/about-us">About Us</a>
이제 링크를 클릭하고 사용자가 알맞은 페이지로 이동한다고 확인하는 테스트를 작성해 봅시다:
public function testBasicExample()
{
$this->visit('/')
->click('About Us')
->seePageIs('/about-us');
}
Form과 관련된 작업하기
라라벨은 또한 form을 테스트하는 여러 메소드들을 제공합니다. type
, select
, check
, attach
, 그리고 press
메소드들은 form의 모든 input들과 상호작용할 수 있도록 해줍니다. 예를 들어 이 form이 애플리케이션의 등록 페이지에 존재한다고 가정해보겠습니다:
<form action="/register" method="POST">
{!! csrf_field() !!}
<div>
Name: <input type="text" name="name">
</div>
<div>
<input type="checkbox" value="yes" name="terms"> Accept Terms
</div>
<div>
<input type="submit" value="Register">
</div>
</form>
이 form을 완성하고 결과를 검사할 수 있는 테스트를 작성할 수 있습니다:
public function testNewUserRegistration()
{
$this->visit('/register')
->type('Taylor', 'name')
->check('terms')
->press('Register')
->seePageIs('/dashboard');
}
물론 form이 라디오 버튼이나 드롭다운 박스와 같은 다른 input을 가지고 있다면 이런 종류의 필드들도 손쉽게 채울 수 있습니다. 다음은 각각의 form을 조작하는 메소드 목록입니다:
메소드 | 설명 |
---|---|
$this->type($text, $elementName) |
주어진 필드에 텍스트를 "채워넣음" |
$this->select($value, $elementName) |
라디오 버튼 또는 드랍다운 필드를 "선택" |
$this->check($elementName) |
체크박스 필드를 "체크" |
$this->attach($pathToFile, $elementName) |
파일을 form에 "추가" |
$this->press($buttonTextOrElementName) |
주어진 텍스 또는 이름의 버튼을 "누르기" |
첨부파일 관련 작업하기
form이 file
input 타입을 포함하고 있다면, attach
메소드를 이용하여 form에 파일을 첨부할 수 있습니다:
public function testPhotoCanBeUploaded()
{
$this->visit('/upload')
->name('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
JSON API 테스트하기
라라벨은 또한 JSON API와 그 결과를 테스트하기 위해 여러 헬퍼들을 제공합니다. 예를 들어, get
, post
, put
, patch
, 그리고 delete
메소드들을 이용하여 다양한 HTTP verb를 가진 request-요청을 할 수 있습니다. 이 메소드들에 손쉽게 데이터와 헤더를 전달할 수도 있습니다. 이를 위해 /user
에 POST
request-요청을 하고 주어진 배열이 JSON 형식으로 반환되었는지 확인하는 테스트를 작성해보겠습니다:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
seeJson
메소드는 주어진 배열을 JSON으로 변환하고 변환된 JSON이 애플리케이션이 반환하는 JSON 응답 중 어느 한곳에 존재하는 것을 확인합니다. 따라서 JSON response-응답이 다른 속성을 가지고 있어도 특정 부분이 있기만 하면 테스트는 통과할 것입니다.
JSON과 완전히 일치하는지 확인하기
주어진 배열이 반환된 JSON과 정확히 일치하는지 확인하고자 한다면, seeJsonEquals
메소드를 사용하면됩니다:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
세션 / 인증
라라벨은 테스팅 중 세션 작업을 하는 데 필요한 여러 헬퍼들을 제공합니다. 먼저, withSession
메소드를 이용하여 주어진 배열을 세션 데이터로 설정 할 수 있습니다. 이것은 애플리케이션에 response-응답을 테스트해보기 전에 데이터를 세션에 로드하는 경우에 유용합니다:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
물론 일반적인 세션의 이용법 중 하나는 인증된 사용자와 같이 사용자 상태를 유지하는 것입니다. actingAs
헬퍼 메소드는 특정 사용자를 현재 사용자로 인증하는 단순한 방법을 제공합니다. 예를 들어, 사용자를 생성하고 인증하기 위해 model factory를 사용할 수 있습니다:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$user = factory(App\User::class)->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
미들웨어 비활성화 시키기
애플리케이션을 테스트할 때 몇몇 테스트에서 미들웨어를 비활성화 하는 편리한 방법을 원할 수도 있습니다. 이는 어떤 미들웨어에도 관계 없이 라우트와 컨트롤러를 테스트할 수 있게 해줍니다. 라라벨은 자동으로 테스트 클래스의 모든 미들웨어를 비활성화할 수 있는 간단한 WithoutMiddleware
트레이트-trait을 포함하고 있습니다:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use WithoutMiddleware;
//
}
몇 개의 테스트 메소드에서만 미들웨어를 비활성화하기를 원한다면 테스트 메소드에서 withoutMiddleware
메소드를 호출하면 됩니다:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
}
사용자 정의-커스텀 HTTP 요청
애플리케이션에 사용자 정의 HTTP request-요청을 하고 Illuminate\Http\Response
객체 전체를 가지고자 한다면 call
메소드를 이용하면 됩니다:
public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
POST
, PUT
, 혹은 PATCH
요청의 경우, 해당 요청과 함께 입력 데이터의 배열을 전달할 수 있습니다. 물론 이 데이터는 request 인스턴스를 통해 라우트와 컨트롤러에서 사용 가능할 수 있습니다:
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
PHPUnit Assertions
라라벨은 PHPUnit 테스트를 위해 몇가지 assertion 메소드를 추가로 제공합니다.
메소드 | 설명 |
---|---|
->assertResponseOk(); |
클라이언트의 응답이 괜찮은(OK) 상태 코드를 갖고 있는지 확인. |
->assertResponseStatus($code); |
클라이언트 응답이 주어진 코드를 가지고 있는지 확인. |
->assertViewHas($key, $value = null); |
응답 뷰가 주어진 데이터의 특정 부분을 가지고 있는지 확인. |
->assertViewHasAll(array $bindings); |
뷰가 주어진 데이터의 특정 목록을 가지고 있는지 확인. |
->assertViewMissing($key); |
응답 뷰가 주어진 데이터의 특정 부분을 가지고 있지 않는지 확인. |
->assertRedirectedTo($uri, $with = []); |
클라이언트가 특정 URI로 리다이렉트되었는지 여부를 확인. |
->assertRedirectedToRoute($name, $parameters = [], $with = []); |
클라이언트가 특정 라우트로 리다이렉트되었는지 확인. |
->assertRedirectedToAction($name, $parameters = [], $with = []); |
클라이언트가 특정 동작으로 리다이렉트되었는지 확인. |
->assertSessionHas($key, $value = null); |
세션이 주어진 값을 가지는지 확인. |
->assertSessionHasAll(array $bindings); |
세션이 주어진 값의 목록을 가지는지 확인. |
->assertSessionHasErrors($bindings = [], $format = null); |
세션이 주어진 오류들을 가지고 있는지 확인. |
->assertHasOldInput(); |
세션이 이전의 입력값을 가지고 있는지 확인. |
데이터베이스 작업하기
라라벨은 또한 데이터베이스를 기반으로 하는 애플리케이션을 손쉽게 테스트 할수 있도록 도와주는 다양한 툴을 제공합니다. 우선 seeInDatabase
헬퍼를 이용하여 데이터베이스 안에 특정 조건을 만족하는 데이터가 존재하는지 확인할 수 있습니다. 예를 들어, users
테이블에 [email protected]
의 email
값을 가진 레코드가 존재하는지 확인하기 위해 다음과 같이 할 수 있습니다:
public function testDatabase()
{
// Make call to application...
$this->seeInDatabase('users', ['email' => '[email protected]']);
}
물론, 편의성을 위해서 seeInDatabase
메소드와 그와 비슷한 다른 헬퍼도 존재합니다. 여러분의 테스트를 보완하기 위해서 PHPUnit에 내장되어 있는 어떤 assertion 메소드라도 사용할 수 있습니다.
각각의 테스트 수행 후에 데이터베이스 재설정하기
종종 이전의 테스트를 위한 데이터가 이어지는 테스트들을 방해하는 것을 방지하기 위해 각 테스트 후에 데이터베이스를 재설정하는 것이 유용합니다.
마이그레이션 이용하기
첫번째 대안은 각각의 테스트 수행 후에 데이터베이스를 롤백하고 다음 테스트를 수행하기 전에 다시 마이그레이션을 실행하는 것입니다. 라라벨은 DatabaseMigrations
트레이트-trait을 제공하여 이를 자동으로 처리 해줍니다. 간단하게 테스트 클래스에서 이 트레이트-trait을 사용하면 됩니다:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
트랜잭션 이용하기
또다른 대안은 데이터베이스 트랜잭션으로 모든 테스트 케이스를 감싸는 것입니다. 이 때에도 라라벨은 편리하게 DatabaseTransactions
트레이트-trait을 제공하여 이것이 자동으로 처리되도록 합니다:
<?php
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()
{
$this->visit('/')
->see('Laravel 5');
}
}
주의: 이 트레이트-trait은 트랜잭션의 기본 데이터베이스 연결-connection만을 둘러싸게 됩니다.
모델 팩토리
테스트를 실행하기 전에 흔히 데이터베이스에 몇몇 레코드를 입력하는 것이 필요할 때가 있습니다. 이 테스트 데이터를 생성할 때 수동으로 각각의 컬럼의 값을 지정하는 대신에 라라벨은 "팩토리"를 사용하여 각각의 Eloquent 모델을 위한 기본 속성의 세트를 정의하도록 해줍니다. 먼저 애플리케이션의 database/factories/ModelFactory.php
파일을 살펴보겠습니다. 이 파일은 바로 사용이 가능한 하나의 팩토리 정의를 가지고 있습니다.
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
팩토리의 정의를 제공하는 클로저 안에서 모든 모델의 속성의 기본 테스트 값을 반환할 수 있습니다. 클로저는 Faker PHP 라이브러리 인스턴스를 전달 받을 것이고, 이는 테스트할 수 있는 다양한 랜덤 데이터를 편리하게 생성할 수 있게 해줍니다.
물론 ModelFactory.php
파일에 여러분의 고유한 팩토리들을 자유롭게 추가할 수 있습니다.
여러가지 팩토리 타입들
여러분은 종종 동일한 Eloquent 모델 클래스를 위해 여러 개의 팩토리들을 가지고자 할수도 있습니다. 예를 들어, 보통 사용자들 외에도 "관리자" 사용자들을 위한 팩토리를 원할 수 있습니다. defineAs
메소드로 이 팩토리들을 정의 할 수 있습니다.
$factory->defineAs(App\User::class, 'admin', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => str_random(10),
'remember_token' => str_random(10),
'admin' => true,
];
});
베이스 사용자 팩토리에서 모든 속성들을 복제하는 대신에, 기본 속성들을 조회하기 위해서 raw
메소드를 사용할 수 있습니다. 속성들을 조회한 뒤에는 필요한 어떤 값들로도 보완 할 수 있습니다.
$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
$user = $factory->raw(App\User::class);
return array_merge($user, ['admin' => true]);
});
테스트 안에서 팩토리 사용하기
팩토리를 정의한 뒤에는, 테스트 안에서나 데이터 시드 파일에서 글로벌 factory
함수를 이용하여 모델 인스턴스를 생성할 수 있습니다. 그럼 모델을 생성하는 몇몇의 예제들을 살펴보겠습니다. 우선 모델을 생성하지만 데이터베이스에 저장하지는 않는, make
메소드를 사용할 것입니다:
public function testDatabase()
{
$user = factory(App\User::class)->make();
// Use model in tests...
}
모델의 몇몇 기본값을 대체하고 싶다면 make
메소드로 값들의 배열을 전달하면 됩니다. 지정된 값들만 대체되고 나머지 값들은 팩토리에 의해 지정된 기본값들을 유지할 것입니다.
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
또한 여러 모델의 컬렉션을 생성하거나 주어진 타입의 모델을 생성할 수도 있습니다:
// Create three App\User instances...
$users = factory(App\User::class, 3)->make();
// Create an App\User "admin" instance...
$user = factory(App\User::class, 'admin')->make();
// Create three App\User "admin" instances...
$users = factory(App\User::class, 'admin', 3)->make();
팩토리 모델의 데이터 저장하기
create
메소드는 모델 인스턴스를 단지 생성하기만 하기 때문에, 데이터베이스에 저장하려면, Eloquent의 save
메소드를 사용하면 됩니다:
public function testDatabase()
{
$user = factory(App\User::class)->create();
// Use model in tests...
}
이 경우에도 create
메소드에 배열을 전달하여 모델의 속성들을 재지정 할 수 있습니다:
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
모델에 관계 추가하기
여러분은 데이터베이스에 여러 모델을 유지하고 싶을 수도 있습니다. 이 예제에서는, 생성된 모델에 관계를 추가할 것입니다. 여러 모델을 생성하기 위해서 create
메소드를 사용할 때 Eloquent 컬렉션 인스턴스가 반환되어 each
와 같이 컬렉션이 제공하는 편리한 함수들을 이용할 수 있도록 해줍니다:
$users = factory(App\User::class, 3)
->create()
->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
Mocking
이벤트 Mocking
라라벨의 이벤트 시스템을 많이 사용 중이라면, 테스트 도중에 어떤 이벤트들을 조용하게 시키거나 mock하고자 할 수도 있을 것입니다. 예를 들어, 사용자 등록을 테스트하고 있다면 UserRegistered
이벤트의 핸들러가 처리되어 "welcome" 이메일이 보내지는 것을 원하지 않을수도 있을 것입니다.
라라벨은 예상되는 이벤트들이 발행했다는 것을 확인하고, 그 이벤트들의 핸들러들이 실행되는 것을 방지해주는 expectsEvents
메소드를 제공합니다:
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->expectsEvents(App\Events\UserRegistered::class);
// Test user registration code...
}
}
모든 이벤트 핸들러들이 실행되는 것을 방지하고 싶다면 withoutEvents
메소드를 사용하면 됩니다:
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->withoutEvents();
// Test user registration code...
}
}
Mocking Jobs
가끔은 애플리케이션에 요청했을 때 특정 작업이 컨트롤러에서 의해서 처리되는지 확인하는 테스트를 하고 싶을 수 있습니다. 이는 작업의 로직으로부터 라우트와 컨트롤러를 분리하여 테스트할 수 있게 해줍니다. 물론 그러고 나서 개별 테스트 클래스 에서 Job 자체에 대해서 테스트할 수 있습니다.
라라벨은 예정된 job이 보내졌다는 것을 확인하지만 실행은 하지 않는 것을 확인하는 편리한 expectsJobs
메소드를 제공합니다:
<?php
class ExampleTest extends TestCase
{
public function testPurchasePodcast()
{
$this->expectsJobs(App\Jobs\PurchasePodcast::class);
// Test purchase podcast code...
}
}
주의: 이 메소드는
DispatchesJobs
트레이트-trait의 dispatch 메소드를 통해 job 이 보내졌는지만을 감지합니다.Queue::push
로 바로 보내진 job은 감지하지 못합니다.
파사드 Mocking
테스트할 때, 종종 라라벨의 파사드의 mock에 호출하고 싶을 수도 있습니다. 예를 들어, 다음의 컨트롤러 액션을 생각해 보겠습니다.:
<?php
namespace App\Http\Controllers;
use Cache;
use Illuminate\Routing\Controller;
class UserController extends Controller
{
/**
* Show a list of all users of the application.
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
shouldReceive
메소드를 사용하여 Cache
파사드 호출을 mock할 수 있고 Mockery의 mock 인스턴스를 반환할 것입니다. 파사드는 실제로는 라라벨의 서비스 컨테이너에서 조회하고 관리하기 때문에 일반적인 정적 클래스보다 훨씬 테스트되기 쉽습니다. 예를 들어 Cache
에 mock 호출을 할 수 있습니다:
<?php
class FooTest extends TestCase
{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
}
주의:
Request
파사드를 mock 해서는 안됩니다. 대신에 테스트를 수행할 때call
과post
와 같은 HTTP 헬퍼 메소드에 원하는 request-입력을 전달하십시오.