PHPUnit과 Laravel: 단위 테스트 작성법 🚀
안녕하세요, 개발자 여러분! 오늘은 정말 핫한 주제인 'PHPUnit과 Laravel을 이용한 단위 테스트 작성법'에 대해 깊이 있게 파헤쳐볼 거예요. 이 글을 읽고 나면 여러분도 테스트 마스터가 될 수 있을 거예요! ㅋㅋㅋ 자, 그럼 시작해볼까요? 🎉
잠깐! 이 글은 '재능넷'의 '지식인의 숲' 메뉴에 등록될 예정이에요. 재능넷은 다양한 재능을 거래하는 플랫폼인데, 여러분의 개발 실력도 충분히 재능이 될 수 있답니다! 😉
1. PHPUnit이 뭐길래? 🤔
PHPUnit은 PHP 프로그래밍 언어를 위한 유닛 테스팅 프레임워크예요. 쉽게 말해서, 여러분이 작성한 코드가 제대로 동작하는지 확인해주는 도구라고 생각하면 돼요. 마치 선생님이 여러분의 숙제를 채점하는 것처럼요! ㅋㅋ
PHPUnit을 사용하면 코드의 각 부분(단위)이 예상대로 작동하는지 자동으로 테스트할 수 있어요.
이게 바로 '단위 테스트'예요. 코드의 작은 부분부터 차근차근 테스트하면, 전체 애플리케이션의 품질을 높일 수 있답니다. 👍
PHPUnit의 특징
- 자동화된 테스트 실행
- 다양한 어서션(assertion) 메소드 제공
- 테스트 결과를 다양한 형식으로 출력
- 테스트 더블(Test Double) 지원
PHPUnit은 마치 여러분의 코드를 위한 개인 트레이너 같아요. 코드가 건강하게 잘 동작하는지 꼼꼼히 체크해주니까요! 💪
위 그림을 보면 PHPUnit이 어떻게 동작하는지 한눈에 알 수 있죠? 코드를 넣으면 PHPUnit이 열심히 일해서 테스트 결과를 뱉어내는 거예요. 마치 맛있는 재료로 요리를 하는 것처럼요! 🍳
2. Laravel과 PHPUnit의 찰떡 궁합 💑
Laravel은 PHP의 대표적인 웹 프레임워크예요. 그리고 Laravel은 기본적으로 PHPUnit을 포함하고 있어요. 이 둘의 조합은 마치 치즈와 와인 같아요. 따로 먹어도 맛있지만, 함께 먹으면 더 맛있죠! ㅋㅋㅋ
Laravel에서 PHPUnit을 사용하면, 애플리케이션의 각 부분을 쉽고 효과적으로 테스트할 수 있어요.
컨트롤러, 모델, 미들웨어 등 Laravel의 모든 구성 요소를 테스트할 수 있답니다. 😎
Laravel에서 PHPUnit 사용하기
Laravel 프로젝트를 생성하면 이미 PHPUnit이 설정되어 있어요. tests
디렉토리를 보면 기본적인 테스트 파일들이 있을 거예요. 이제 여러분이 할 일은 이 파일들을 채워나가는 거예요!
php artisan make:test UserTest
위 명령어를 실행하면 tests/Feature
디렉토리에 UserTest.php
파일이 생성돼요. 이제 이 파일에 테스트 코드를 작성하면 되는 거죠!
팁! Laravel의 artisan
명령어를 사용하면 테스트 파일을 쉽게 생성할 수 있어요. 마치 마법사가 주문을 외우는 것처럼 간단하죠! 🧙♂️
3. 단위 테스트의 기본 구조 🏗️
단위 테스트를 작성할 때는 기본적인 구조를 따라야 해요. 이 구조는 마치 요리 레시피와 같아요. 재료를 준비하고, 요리하고, 맛을 확인하는 과정과 비슷하죠!
테스트의 3A 원칙
- Arrange (준비): 테스트에 필요한 객체와 데이터를 준비해요.
- Act (실행): 테스트하려는 메소드나 기능을 실행해요.
- Assert (검증): 실행 결과가 예상과 일치하는지 확인해요.
이 3A 원칙을 따르면 테스트 코드가 깔끔하고 이해하기 쉬워져요. 마치 잘 정리된 방처럼요! 😊
위 그림을 보면 테스트의 흐름이 한눈에 들어오죠? Arrange에서 시작해서 Act를 거쳐 Assert로 끝나는 거예요. 마치 멋진 공연을 보는 것 같아요! 🎭
테스트 예시
자, 이제 실제로 테스트를 어떻게 작성하는지 볼까요? 아래는 간단한 사용자 등록 기능을 테스트하는 코드예요.
public function test_user_can_register()
{
// Arrange
$userData = [
'name' => '홍길동',
'email' => 'hong@example.com',
'password' => 'password123',
];
// Act
$response = $this->post('/register', $userData);
// Assert
$response->assertRedirect('/home');
$this->assertDatabaseHas('users', ['email' => 'hong@example.com']);
}
이 테스트 코드를 보면 3A 원칙이 잘 적용되어 있죠? 준비하고, 실행하고, 검증하는 과정이 명확해요. 마치 요리 프로그램에서 요리사가 차근차근 요리를 만드는 것 같아요! 👨🍳
참고: 재능넷에서는 이런 테스트 작성 능력을 가진 개발자들의 재능이 높이 평가받고 있어요. 테스트 작성 실력을 키우면 여러분의 가치도 올라갈 거예요! 💼
4. Laravel에서 자주 사용되는 PHPUnit 어서션 🧪
PHPUnit에는 다양한 어서션(assertion) 메소드가 있어요. 이 메소드들은 테스트 결과를 검증하는 데 사용돼요. 마치 요리사가 음식의 맛을 확인하는 것처럼요! 😋
주요 어서션 메소드
assertEquals($expected, $actual)
: 두 값이 같은지 확인해요.assertTrue($condition)
: 조건이 참인지 확인해요.assertFalse($condition)
: 조건이 거짓인지 확인해요.assertNull($actual)
: 값이 null인지 확인해요.assertNotNull($actual)
: 값이 null이 아닌지 확인해요.assertContains($needle, $haystack)
: 배열이나 문자열에 특정 값이 포함되어 있는지 확인해요.
이런 어서션 메소드들을 잘 활용하면 테스트를 더욱 정확하고 세밀하게 할 수 있어요.
마치 현미경으로 세포를 관찰하는 것처럼 코드의 작은 부분까지 꼼꼼히 체크할 수 있죠! 🔬
Laravel 전용 어서션
Laravel은 PHPUnit의 기본 어서션 외에도 웹 애플리케이션 테스트에 특화된 추가 어서션을 제공해요. 이건 마치 Laravel이 PHPUnit에게 특별한 초능력을 준 것 같아요! ㅋㅋㅋ
$response->assertStatus($code)
: HTTP 응답 상태 코드를 확인해요.$response->assertJson($data)
: JSON 응답을 확인해요.$response->assertViewIs($value)
: 반환된 뷰의 이름을 확인해요.$this->assertDatabaseHas($table, array $data)
: 데이터베이스에 특정 데이터가 있는지 확인해요.$this->assertDatabaseMissing($table, array $data)
: 데이터베이스에 특정 데이터가 없는지 확인해요.
이런 Laravel 전용 어서션을 사용하면 웹 애플리케이션의 특성에 맞는 테스트를 더 쉽게 작성할 수 있어요. 마치 전문 도구를 사용하는 장인처럼요! 🛠️
위 그림은 Laravel의 어서션 도구상자를 표현한 거예요. PHPUnit의 기본 어서션 위에 Laravel만의 특별한 도구들이 추가되어 있죠. 이 도구상자로 여러분은 어떤 테스트든 척척 해낼 수 있을 거예요! 💪
5. 모의 객체(Mock)와 스텁(Stub) 사용하기 🎭
테스트를 작성하다 보면 외부 서비스나 데이터베이스 같은 실제 객체를 사용하기 어려운 경우가 있어요. 이럴 때 사용하는 게 바로 모의 객체(Mock)와 스텁(Stub)이에요. 이들은 마치 영화 세트장 같아요. 진짜처럼 보이지만 실제로는 가짜랍니다! ㅋㅋ
모의 객체(Mock)란?
모의 객체는 실제 객체의 행동을 흉내 내는 가짜 객체예요. 테스트 중에 특정 메소드가 호출되었는지, 어떤 인자로 호출되었는지 등을 확인할 수 있어요.
모의 객체를 사용하면 복잡한 외부 시스템과의 상호작용을 단순화할 수 있어요.
예를 들어, 결제 시스템을 테스트할 때 실제로 돈이 오가지 않아도 되니까 편리하죠! 💸
스텁(Stub)이란?
스텁은 미리 정의된 응답을 반환하는 객체예요. 실제 객체의 특정 메소드 호출에 대해 항상 같은 결과를 반환하도록 만들 수 있죠.
스텁을 사용하면 테스트 환경을 더 잘 통제할 수 있어요. 마치 날씨를 마음대로 조절할 수 있는 실내 스튜디오 같아요! ☀️🌧️
Laravel에서 모의 객체와 스텁 사용하기
Laravel은 PHPUnit의 모의 객체 기능을 확장해서 더 쉽게 사용할 수 있게 해줘요. 예를 들어, Mockery
라이브러리를 사용해 모의 객체를 만들 수 있죠.
use Mockery;
public function testOrderCreation()
{
// 결제 서비스의 모의 객체 생성
$paymentService = Mockery::mock(PaymentService::class);
// 모의 객체의 동작 정의
$paymentService->shouldReceive('processPayment')
->once()
->with(100)
->andReturn(true);
// 모의 객체를 사용해 주문 생성
$order = new Order($paymentService);
$result = $order->create(['amount' => 100]);
// 결과 확인
$this->assertTrue($result);
}
이 예제에서는 PaymentService
의 모의 객체를 만들고, processPayment
메소드가 한 번 호출되고 100을 인자로 받아 true를 반환하도록 설정했어요. 이렇게 하면 실제 결제 없이도 주문 생성 로직을 테스트할 수 있답니다! 👍
이 그림에서 볼 수 있듯이, 모의 객체와 스텁은 테스트 환경에서 실제 객체를 대신해요. 마치 영화에서 스턴트맨이 위험한 장면을 대신하는 것처럼요! 🎬
팁! 모의 객체와 스텁을 잘 활용하면 테스트 코드를 더 안정적이고 예측 가능하게 만들 수 있어요. 재능넷에서도 이런 고급 테스트 기법을 아는 개발자들이 인기가 많답니다! 😎
6. 데이터베이스 테스트 작성하기 💾
웹 애플리케이션에서 데이터베이스는 정말 중요한 부분이에요. 그래서 데이터베이스와 관련된 로직을 테스트하는 것도 매우 중요하죠. Laravel은 데이터베이스 테스트를 쉽게 할 수 있는 도구들을 제공해요. 이건 마치 데이터베이스 전문 요리사를 고용한 것 같아요! ㅋㅋㅋ
테스트 데이터베이스 설정
데이터베이스 테스트를 할 때는 실제 데이터베이스를 사용하면 안 돼요. 대신 테스트용 데이터베이스를 따로 만들어야 해요. Laravel의 phpunit.xml
파일에서 이를 설정할 수 있어요.
이렇게 설정하면 테스트할 때 메모리 상의 SQLite 데이터베이스를 사용하게 돼요. 실제 데이터에 영향을 주지 않으면서도 빠르게 테스트할 수 있답니다! 👍
마이그레이션과 시딩
테스트를 시작하기 전에 데이터베이스 구조를 만들고 테스트 데이터를 넣어야 해요. Laravel에서는 이를 마이그레이션과 시딩이라고 해요.
마이그레이션은 데이터베이스 구조를 코드로 정의하는 거예요.
테이블을 만들고, 수정하고, 삭제하는 등의 작업을 코드로 관리할 수 있죠. 마치 데이터베이스의 설계도를 그리는 것 같아요! 📐
시딩은 테스트용 데이터를 데이터베이스에 넣는 작업이에요. 이렇게 하면 항상 같은 상태에서 테스트를 시작할 수 있어요. 마치 실험실에서 실험 조건을 동일하게 맞추는 것처럼요! 🧪
use Illuminate\Foundation\Testing\RefreshDatabase;
class UserTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_be_created()
{
$userData = [
'name' => '홍길동',
'email' => 'hong@example.com',
'password' => bcrypt('password123'),
];
$user = User::create($userData);
$this->assertDatabaseHas('users', ['email' => 'hong@example.com']);
}
}
이 예제에서 RefreshDatabase
트레이트를 사용하면 매 테스트마다 데이터베이스를 초기화하고 마이그레이션을 실행해요. 그리고 assertDatabaseHas
메소드로 데이터가 제대로 저장되었는지 확인하고 있죠.
팩토리 사용하기
테스트 데이터를 만들 때 매번 직접 배열을 작성하는 건 귀찮은 일이에요. 이럴 때 사용하는 게 바로 팩토리(Factory)예요. 팩토리는 테스트 데이터를 자동으로 생성해주는 공장 같은 거예요! 🏭
use Database\Factories\UserFactory;
public function test_user_can_login()
{
$user = UserFactory::new()->create([
'password' => bcrypt('testpassword'),
]);
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'testpassword',
]);
$response->assertRedirect('/home');
$this->assertAuthenticatedAs($user);
}
이 예제에서는 UserFactory
를 사용해 테스트용 사용자를 만들고 있어요. 팩토리를 사용하면 필요한 데이터만 지정하고 나머지는 자동으로 생성되니까 정말 편리하죠! 😊
이 그림은 데이터베이스 테스트의 전체 과정을 보여주고 있어요. 마이그레이션으로 구조를 만들고, 시딩으로 데이터를 넣은 다음, 팩토리로 테스트 데이터를 생성하고 테스트를 실행하는 거죠. 마치 요리 프로그램에서 재료 준비부터 요리, 시식까지의 과정을 보는 것 같아요! 🍳👨🍳
7. 테스트 커버리지 확인하기 🎯
테스트를 열심히 작성했다면, 이제 얼마나 많은 코드를 테스트했는지 확인해볼 차례예요. 이걸 테스트 커버리지라고 해요. 테스트 커버리지는 마치 학교 시험에서 얼마나 많은 범위를 공부했는지 체크하는 것과 비슷해요! 📚
PHPUnit의 커버리지 리포트
PHPUnit은 테스트 커버리지 리포트를 생성할 수 있는 기능을 제공해요. 이 리포트를 통해 어떤 코드가 테스트되었고, 어떤 코드가 테스트되지 않았는지 한눈에 볼 수 있죠.
커버리지 리포트를 보면 테스트가 필요한 부분을 쉽게 찾을 수 있어요.
마치 보물지도를 보고 숨겨진 보물을 찾는 것처럼 재미있을 거예요! 🗺️💎
커버리지 리포트 생성하기
Laravel 프로젝트에서 커버리지 리포트를 생성하려면 다음 명령어를 사용하면 돼요:
./vendor/bin/phpunit --coverage-html coverage
이 명령어를 실행하면 coverage
디렉토리에 HTML 형식의 커버리지 리포트가 생성돼요. 이 리포트를 브라우저로 열어보면 예쁜 그래프와 함께 상세한 커버리지 정보를 볼 수 있답니다! 😍
커버리지 목표 설정하기
테스트 커버리지는 높을수록 좋지만, 100%를 달성하는 건 현실적으로 어려울 수 있어요. 그래서 보통 팀이나 프로젝트별로 목표 커버리지를 정해요. 예를 들어, "핵심 비즈니스 로직은 90% 이상, 나머지는 70% 이상" 이런 식으로요.
목표를 정하고 나면, CI/CD 파이프라인에 커버리지 체크를 추가할 수 있어요. 이렇게 하면 커버리지가 떨어질 때마다 경고를 받을 수 있죠. 마치 건강검진에서 특정 수치가 떨어지면 경고해주는 것처럼요! 🏥
이 그림은 테스트 커버리지 대시보드의 예시예요. 라인 커버리지, 분기 커버리지 등 다양한 지표를 한눈에 볼 수 있죠. 목표 커버리지도 함께 표시되어 있어서 현재 상태를 쉽게 파악할 수 있어요. 마치 자동차 대시보드에서 속도와 연료 상태를 확인하는 것처럼 편리하답니다! 🚗💨
참고: 재능넷에서는 높은 테스트 커버리지를 가진 프로젝트가 더 신뢰를 받는 경향이 있어요. 커버리지를 높이는 것은 여러분의 코드 품질을 높이는 좋은 방법이 될 수 있답니다! 💼🌟
8. 테스트 주도 개발(TDD) 시작하기 🚀
자, 이제 우리는 테스트 작성 방법을 잘 알게 되었어요. 그런데 "테스트를 언제 작성해야 할까?" 라는 의문이 들 수 있죠. 여기서 소개할 개념이 바로 테스트 주도 개발(Test-Driven Development, TDD)이에요. TDD는 테스트를 먼저 작성하고, 그 다음에 실제 코드를 작성하는 방법이에요. 마치 요리 레시피를 먼저 쓰고 그 다음에 요리를 하는 것과 비슷해요! 👨🍳📝
TDD의 기본 사이클
- Red: 실패하는 테스트를 작성해요.
- Green: 테스트를 통과하는 최소한의 코드를 작성해요.
- Refactor: 코드를 개선하고 중복을 제거해요.
이 사이클을 반복하면서 조금씩 기능을 구현해 나가는 거예요.
마치 벽돌을 하나씩 쌓아 집을 짓는 것처럼요! 🏗️
TDD의 장점
- 코드의 품질이 향상돼요.
- 버그를 빨리 발견할 수 있어요.
- 리팩토링이 쉬워져요.
- 문서화 효과가 있어요.
TDD를 실천하면 마치 안전벨트를 매고 운전하는 것처럼 안정감 있게 개발할 수 있어요. 물론 처음에는 조금 불편할 수 있지만, 익숙해지면 그 가치를 충분히 느낄 수 있을 거예요! 🚗💺
Laravel에서 TDD 실습하기
자, 이제 간단한 예제로 TDD를 실습해볼까요? 사용자의 나이를 확인하고 성인인지 아닌지 판단하는 기능을 만들어봐요.
// tests/Unit/UserTest.php
public function test_user_is_adult()
{
$user = new User(['age' => 20]);
$this->assertTrue($user->isAdult());
$user = new User(['age' => 17]);
$this->assertFalse($user->isAdult());
}
이 테스트를 작성하고 실행하면 당연히 실패할 거예요. 왜냐하면 아직 User
클래스에 isAdult
메소드가 없기 때문이죠. 이제 이 테스트를 통과시키는 코드를 작성해볼게요.
// app/Models/User.php
public function isAdult()
{
return $this->age >= 18;
}
이렇게 코드를 작성하고 테스트를 다시 실행하면 통과할 거예요. 이제 필요하다면 코드를 리팩토링할 수 있어요. 예를 들어, 성인 나이 기준을 상수로 분리한다든지 하는 식으로요.
이 그림은 TDD의 Red-Green-Refactor 사이클을 보여주고 있어요. 이 사이클을 계속 반복하면서 코드를 발전시켜 나가는 거죠. 마치 자전거 페달을 계속 밟아 앞으로 나아가는 것처럼요! 🚴♂️💨
팁! TDD는 처음에는 어렵고 시간이 많이 걸릴 수 있어요. 하지만 꾸준히 연습하면 점점 익숙해지고, 결국에는 개발 속도도 빨라질 거예요. 재능넷에서도 TDD를 실천하는 개발자들이 점점 늘어나고 있답니다! 💪😊
9. 테스트 최적화와 성능 향상 🚀
테스트를 많이 작성하다 보면 테스트 실행 시간이 길어질 수 있어요. 테스트가 너무 오래 걸리면 개발 속도가 느려지고, CI/CD 파이프라인도 지연될 수 있죠. 그래서 테스트 최적화는 정말 중요해요. 마치 자동차 엔진을 튜닝해서 더 빠르게 만드는 것과 같아요! 🏎️💨
병렬 테스팅
PHPUnit 9.5 버전부터는 병렬로 테스트를 실행할 수 있는 기능을 제공해요. 이를 통해 테스트 실행 시간을 크게 줄일 수 있죠.
./vendor/bin/phpunit --parallel
병렬 테스팅을 사용하면 여러 개의 테스트를 동시에 실행할 수 있어요.
마치 여러 명의 요리사가 동시에 요리를 하는 것처럼, 테스트 실행 시간을 크게 단축할 수 있답니다! 👨🍳👩🍳👨🍳
데이터베이스 트랜잭션
데이터베이스를 사용하는 테스트의 경우, 각 테스트마다 데이터베이스를 초기화하는 데 시간이 많이 걸릴 수 있어요. 이럴 때 데이터베이스 트랜잭션을 사용하면 좋아요.
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseTransactions;
// 테스트 메소드들...
}
DatabaseTransactions
트레이트를 사용하면, 각 테스트가 끝날 때마다 변경사항이 롤백돼요. 덕분에 데이터베이스를 매번 초기화할 필요가 없어지죠. 마치 화이트보드에 그림을 그리고 지우는 것을 반복하는 대신, 투명한 필름 위에 그림을 그리고 간단히 벗겨내는 것과 같아요! 🎨
테스트 더블 활용
외부 서비스나 무거운 계산이 필요한 부분은 테스트 더블(Test Double)을 활용하면 좋아요. 모의 객체(Mock)나 스텁(Stub)을 사용하면 테스트 속도를 크게 높일 수 있죠.
public function test_order_shipping()
{
$shippingService = $this->createMock(ShippingService::class);
$shippingService->method('calculateCost')->willReturn(10.00);
$order = new Order($shippingService);
$this->assertEquals(10.00, $order->getShippingCost());
}
이 예제에서는 실제 배송비 계산 대신 모의 객체를 사용했어요. 덕분에 복잡한 계산 과정 없이 빠르게 테스트를 진행할 수 있죠. 마치 실제 비행기를 타는 대신 비행 시뮬레이터를 사용하는 것과 비슷해요! ✈️🎮