0. 개요
라라벨은 기본적으로 테스트를 권장합니다.
프로젝트 생성 시 바로 아래 경로로 tests 폴더가 생성됩니다.
이번에 만들 프로젝트는 간단한 추첨 기능을 포함한 사이트입니다, 여기에 테스트 기능을 적용해서 해볼 것입니다.
1. 프로젝트 생성 및 파일 생성
우선 프로젝트를 만들어 주시고
composer create-project laravel/laravel laravel_testing "9.*"
cd laravel_testing
composer install
php artisan key:generate
.env 파일 생성하여 DB 연결 작업 및 환경에 따라 가상 호스트 작업을 마쳐 주세요.
기능에 필요한 사용할 파일들을 artisan을 통해 만들어 줍시다.
php artisan make:model Member -m
php artisan make:controller MemberController
2. 테스팅 사이트 생성
테스팅 자체에는 사이트가 없어도 상관없습니다.
하지만 테스트 기능을 이해하는데 도움을 주고자 만들어봅시다.
라우팅 설정을 해줍시다.
routes/web.php
Route::get('/', [MemberController::class, 'index']);
Route::post('/', [MemberController::class, 'store']);
Route::get('/draw', [MemberController::class, 'draw']);
database/migrations/yyyy_mm_dd_000000_create_members_table.php
마이그레이션 수정하여 Member 테이블을 정의해줍시다.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('members', function (Blueprint $table) {
$table->id();
// 일부러 테스트를 위해 unique 을 안줌
$table->string('name',128);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('members');
}
};
테이블을 만들어줍시다.
php artisan migrate
이제 Controller에서 메소드들을 만들어 뷰 파일들과 연결해줄 준비를 합니다.
app/Http/Controller/MemberController.php
<?php
namespace App\Http\Controllers;
use App\Http\Traits\MemberTrait;
use App\Models\Member;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class MemberController extends Controller
{
// 다음 코드에서 만들 것 입니다.
use MemberTrait;
private Member $member;
// 모델 의존성 주입
public function __construct(Member $member){
$this->member = $member;
}
public function index(){
return view('index')->with('list', $this->member->all());
}
public function store(Request $request){
$validated = $request->validate([
'name' => 'required'
]);
$this->member->create($validated);
return redirect('/');
}
public function draw(){
// getMemberDraw는 Trait 기능을 사용해서 포함시킨 것이므로 지금은 에러가 발생합니다.
return view('draw')->with('member', $this->getMemberDraw());
}
}
위 코드에서 MemberTrait는 트레이트(Trait)이라 하며 공통적으로 사용하는 메소드나 값들의 재사용을 목적으로 한 기능입니다.
아래와 같은 특징을 지닙니다.
- 기존 class 대신에 Trait 키워드를 사용합니다.
- class 보다는 Interface와 비슷하여, 여러 개를 상속받을 수 있습니다.
- use Trait명으로 포함하면 해당 클래스에서 $this로 접근하여 Trait에서 선언했던 메소드를 쓸 수 있습니다.
마지막 특징 때문에 기능을 분리해서 여러 서비스 컨테이너에서 사용하기 용이합니다.
이번 경우에는 Controller 뿐만 아니라 Test에서도 사용할 것이라 Trait로 만들어 줍시다.
Trait는 Artisan에서 지원하는 명령어가 없으므로 아래 경로로 직접 만들어 줍시다.
app/Http/Traits/MemberTrait.php
<?php
namespace App\Http\Traits;
use App\Models\Member;
use Illuminate\Support\Arr;
trait MemberTrait {
public function getMemberDraw(){
// member 테이블에서 name을 뽑은 후 랜덤으로 가져오는 기능입니다.
return Arr::random(Member::pluck('name')->toArray());
}
}
이제 view 파일을 만들어줍시다, 테스트를 알아보는 게 목적이므로 복사해서 붙여 넣기를 추천합니다.
가볍게 부트스트랩을 통해 스타일링했습니다.
resource/views/index.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Laravel Test</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<div class="row mt-5 mb-5">
<div class="col-10 offset-1 mt-5">
<div class="card">
<div class="card-body">
<form method="POST" action="/">
@csrf
<div class="row">
<div class="col-md-12">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" class="form-control" placeholder="Name">
@if ($errors->has('name'))
<span class="text-danger">{{ $errors->first('name') }}</span>
@endif
</div>
</div>
</div>
<div class="form-group text-center">
<button class="btn btn-success btn-submit">저장</button>
</div>
<div class="form-group text-center">
<a href="/draw">
<button class="btn btn-success" type="button">추첨하기</button>
</a>
</div>
</form>
<hr/>
<strong>List:</strong>
<ul class="list-group">
@foreach ($list as $item)
<li class="list-group-item">{{$item->id}}<span class="border m-3"></span>{{$item->name}}</li>
@endforeach
</ul>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
resource/views/draw.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Laravel Test</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<div class="row mt-5 mb-5">
<div class="col-10 offset-1 mt-5">
<div class="card">
<div class="card-body">
<strong>추첨 : {{$member}}</strong>
<div class="form-group text-center">
<a href="/">
<button class="btn btn-success" type="button">이전으로</button>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
마지막으로 모델에서 데이터 저장을 위해 fillable 변수를 수정해서 name 컬럼을 저장할 수 있게 만들어줍시다.
app/Models/Member.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Member extends Model
{
use HasFactory;
protected $fillable = ['name'];
}
여기까지 했다면 다음과 같이 간단한 추첨 사이트가 만들어졌을 것입니다.
이걸 토대로 test를 작성해봅시다.
3. Testing
라라벨에서 Test는 크게 2가지입니다.
- Unit 테스팅
- 단위 테스트이며 라라벨 애플리케이션을 실행시키지 않으므로 DB 연결이 불가능합니다.
- CLI 테스팅시에 용이합니다.
- Feature 테스팅
- 기능 테스트이며 주로 이 테스트를 많이 사용합니다, DB 연결이 가능합니다.
제가 이번 포스팅에서 사용할 테스팅은 Feature 테스팅입니다.
라라벨 프로젝트를 만들면 있는 tests 폴더 내부를 보시면 Feature과 Unit이 존재합니다.
우리는 Feature Test를 만들 것이므로 아래 명령어를 사용합시다.
php artisan make:test MemberTest
만약 Unit 테스팅을 만든다면 --unit 옵션을 만들 면 됩니다.
php artisan make:test MemberTest --unit
위 명령어를 통해서 Feature 디렉터리 아래에 파일이 만들어졌을 것입니다.
그리고 기존 .env를 복사하여 .env.testing을 만들어줍시다.
여기에는 테스팅 데이터베이스를 별도로 만들어 연결해줍시다.
이 테이블에 테스트로 만들어지는 데이터가 들어가게 되는 것입니다.
그러므로 실제 데이터베이스와의 분리가 필수입니다.
.env.testing
DB 연결 후 아래 명령어로 migration을 테스트 데이터베이스에 적용해줍시다.
php artisan cache:clear
php artisan config:cache
php artisan --env=testing migrate
테스트에서는 실제 데이터를 백업해와서 사용하면 좋지만, 편의성을 위해 팩토리를 만들어서 넣어줍시다.
팩토리 기능은 테이블에 대용량 더미 데이터 할당에 쓰기 좋습니다.
php artisan make:factory MemberFactory
아래처럼 내용을 수정해주면 fake 기능을 통해 이름을 더미 데이터로 생성할 수 있습니다.
<?php
...
public function definition()
{
return [
'name' => fake()->name(),
];
}
}
마지막으로 테스트 클래스를 정의해줍시다.
tests/Feature/MemberTest.php
<?php
namespace Tests\Feature;
use App\Http\Traits\MemberTrait;
use App\Models\Member;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class MemberTest extends TestCase
{
use MemberTrait;
// 이거 쓰면 시작과 끝에 데이터베이스 초기화
use RefreshDatabase;
/**
* Member 테이블에 중복 데이터가 있는 지 체크합니다.
* @return void
*/
public function test_member_unique_check()
{
$memberNames = (new Member)->pluck('name')->toArray();
// 중복 제거한 결과와 중복 제거하지 않은 결과의 수가 같으면 성공입니다.
$this->assertTrue(
count($memberNames) == count(array_unique($memberNames))
);
}
/**
* Factory로 데이터가 잘 추가되는 지 체크합니다.
* @return void
*/
public function test_member_factory(){
$count1 = (new Member)->all()->count();
Member::factory()->create();
$count2 = (new Member)->all()->count();
// Factory 로 값이 성공적으로 추가되서 아래 조건식이 참이면 성공입니다.
$this->assertTrue($count1+1 == $count2);
}
/**
* 이게 테스트의 의미에 가장 부합하는 테스트로 실제 사용 중인 기능을 테스트합니다.
* @return void
*/
public function test_member_draw(){
// 5개의 더미 데이터 생성 후
Member::factory()->count(5)->create();
// MemberTrait 로 만든 기능을 실행합니다.
$memberDraw = $this->getMemberDraw();
// 나온 결과가 members 테이블에 있는지 확인합니다
$member = Member::where('name', '=', $memberDraw)->first();
// 결과가 있으면 성공입니다.
$this->assertTrue($member != null);
}
}
테스트 클래스를 만들었다면 결과를 확인해봅시다.
test 실행 명령어는 아래와 같습니다.
php artisan test
위 사진처럼 PASS처리가 되면 성공입니다.
PASS의 여부는 위 테스트 클래스에서 사용한 assets 함수들의 결과입니다.
예시에서는 assertTrue로 조건식이 참이면 PASS가 떴었지만 이 밖에도 많은 메소드들이 존재합니다.
https://laravel.com/docs/9.x/http-tests#available-assertions
만약 기존 코드를 수정해서 문제가 생겼을 경우 어떻게 되는 지도 테스트해봅시다.
app/Http/Traits/MemberTrait.php 의 getMemberDraw를 DB에 존재하지 않는 값을 던져주게 바꿔줍시다.
...
public function getMemberDraw(){
return 'Hello world';
}
...
이러면 아래와 같이 에러가 발생합니다.
이렇게 코드가 수정되었을 경우 실제 페이지에 들어가서 추첨하지 않고 테스트 클래스를 사용함으로써
기존보다 빠르게 원인을 파악할 수 있습니다.
마지막으로 지금까지는 순차적으로 테스트가 진행되었는데 테스트를 병행으로 돌릴 수 있습니다.
병행이므로 훨씬 속도가 빨라지는 대신 순차적으로 실행이 되지 않는 점을 유의해야 합니다.
php artisan test --parallel
=> 성공 시
=> 실패 시
test에는 option으로 PHP Unit의 기능을 옵션으로 넣을 수 있습니다
// Feature 테스트에서, 실패시 중지
php artisan test --testsuite=Feature --stop-on-failure
하지만 병행성 처리시에는 위 옵션을 넣지 못하므로 참고해주시면 됩니다.
이렇게 라라벨 테스트 기능을 적용해보았습니다.
따라 하시는 도중 막히는 부분 또는 발생하는 에러 혹은 맞지 않는 설명에 대해 피드백해주신다면 감사합니다.
'PHP > Laravel' 카테고리의 다른 글
[Laravel9] 라라벨 DB 백업 명령어 만들기 및 자동화 with Artisan console (0) | 2022.11.01 |
---|---|
[Laravel9] 라라벨 아티즌 콘솔 명령어 만들기(Artisan console) (2) | 2022.10.31 |
[Laravel9] 라라벨 이메일 보내기 with Google SMTP, Markdown (0) | 2022.10.24 |
Laravel 9 Observer 써보자, 옵저버로 이력(히스토리) 쌓기 (0) | 2022.09.23 |
Laravel 라라벨 스케줄링(Scheduling)하기 with crontab (0) | 2022.09.15 |