배포 전 안전장치: PHP 테스트로 트랜잭션 롤백 검증
레거시 코드를 리팩토링하면 “잘 동작하는지 어떻게 확인하지?”가 항상 문제였다. 테스트 DB를 쓰면 운영 데이터랑 조건이 달라서 미심쩍고, 운영 DB를 쓰면 데이터가 오염된다. 트랜잭션 롤백으로 이 딜레마를 해결했다.
배경
학원 운영관리 시스템의 PHP 레거시 코드는 컨트롤러에 모든 비즈니스 로직이 집중되어 있었다.
문제점:
- 컨트롤러 메서드 하나에 수백 줄의 로직
- DB 쿼리, 비즈니스 로직, 응답 생성이 모두 섞여 있음
- 테스트 코드 없음
Service-Repository 패턴으로 리팩토링하기로 했지만, 배포 전 운영 DB에서 실제 동작을 확인해야 했다.
문제 발생
고민:
- 로컬 환경만으로는 운영 환경 이슈를 발견하기 어려움
- 스테이징 환경이 없어 운영 DB로 직접 테스트해야 함
- 운영 DB로 테스트하면 실제 데이터가 저장될 위험
해결 방법
PHP 테스트 코드를 작성하고, 트랜잭션 롤백을 추가했다. 테스트가 성공하면 배포되게 설정했다.
핵심:
- 목 데이터로 실제 API 테스트
- 테스트 종료 시 자동으로 트랜잭션 롤백
- 테스트 성공하면 배포 진행
테스트 코드
DatabaseTransactions 트레이트 활용:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class StudentApiTest extends TestCase
{
use DatabaseTransactions; // 테스트 종료 시 자동 롤백
/**
* 학생 등록 API 테스트
*/
public function test_student_registration()
{
// 목 데이터
$data = [
'name' => '홍길동_TEST',
'phone' => '010-1234-5678',
'email' => 'test@example.com',
'parent_phone' => '010-9876-5432',
];
// API 호출
$response = $this->postJson('/api/students', $data);
// 응답 검증
$response->assertStatus(201)
->assertJson([
'message' => '학생 등록 완료',
]);
// DB 확인
$this->assertDatabaseHas('students', [
'name' => $data['name'],
'phone' => $data['phone'],
]);
}
/**
* 수업 배정 API 테스트
*/
public function test_class_assignment()
{
// 목 데이터
$student = [
'name' => '김철수_TEST',
'phone' => '010-1111-2222',
];
$class = [
'name' => '수학 심화반',
'start_time' => '14:00',
];
// 학생 등록
$studentResponse = $this->postJson('/api/students', $student);
$studentId = $studentResponse->json('data.id');
// 수업 생성
$classResponse = $this->postJson('/api/classes', $class);
$classId = $classResponse->json('data.id');
// 수업 배정
$assignmentData = [
'student_id' => $studentId,
'class_id' => $classId,
];
$response = $this->postJson('/api/class-assignments', $assignmentData);
// 검증
$response->assertStatus(201);
$this->assertDatabaseHas('class_assignments', $assignmentData);
}
/**
* 출석 체크 API 테스트
*/
public function test_attendance_check()
{
// 목 데이터
$student = $this->postJson('/api/students', [
'name' => '이영희_TEST',
'phone' => '010-3333-4444',
])->json('data');
$attendanceData = [
'student_id' => $student['id'],
'date' => '2025-01-15',
'status' => 'present',
];
// API 호출
$response = $this->postJson('/api/attendances', $attendanceData);
// 검증
$response->assertStatus(201);
$this->assertDatabaseHas('attendances', $attendanceData);
}
}
Laravel 없이 직접 트랜잭션 관리:
<?php
namespace Tests;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected $pdo;
protected function setUp(): void
{
parent::setUp();
// DB 연결
$this->pdo = new PDO(
'mysql:host=localhost;dbname=production',
'user',
'password'
);
// 트랜잭션 시작
$this->pdo->beginTransaction();
}
protected function tearDown(): void
{
// 트랜잭션 롤백
$this->pdo->rollBack();
parent::tearDown();
}
}
Jenkins 파이프라인
테스트 성공 시에만 배포:
pipeline {
agent any
stages {
stage('Test') {
steps {
echo '=== 배포 전 검증 시작 ==='
sh 'php vendor/bin/phpunit tests/Feature/StudentApiTest.php'
}
}
stage('Deploy') {
when {
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
steps {
echo '테스트 성공! 배포를 진행합니다.'
sh 'git pull origin main'
sh 'composer install --no-dev'
sh 'php artisan migrate --force'
sh 'php artisan config:cache'
}
}
}
post {
failure {
echo '테스트 실패! 배포를 중단합니다.'
}
}
}
배포 방법:
- Jenkins에서 재생 버튼 클릭
- Test 스테이지 자동 실행
- 테스트 성공 시 Deploy 스테이지 진행
- 테스트 실패 시 배포 중단
실행 결과
Started by user admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] echo
=== 배포 전 검증 시작 ===
[Pipeline] sh
+ php vendor/bin/phpunit tests/Feature/StudentApiTest.php
PHPUnit 9.5.10
... 3 / 3 (100%)
Time: 00:02.156, Memory: 18.00 MB
OK (3 tests, 9 assertions)
[Pipeline] }
[Pipeline] stage
[Pipeline] { (Deploy)
[Pipeline] echo
테스트 성공! 배포를 진행합니다.
[Pipeline] sh
+ git pull origin main
Already up to date.
[Pipeline] sh
+ composer install --no-dev
Installing dependencies from lock file
...
[Pipeline] }
[Pipeline] End of Pipeline
Finished: SUCCESS
결과
성과:
- 운영 DB로 배포 전 검증, 데이터 오염 없음
- 테스트 자동화로 배포 안정성 확보
- 테스트 실패 시 배포 자동 중단
프로세스:
- 목 데이터로 실제 API 호출
- 테스트 종료 시 자동 롤백
- 테스트 성공 시에만 배포 진행
배운 점
1. 트랜잭션 롤백으로 안전한 검증
운영 DB로 테스트해도 트랜잭션 롤백으로 데이터 오염을 완전히 방지할 수 있다.
2. 테스트 자동화가 배포 안정성을 높인다
배포 전 자동으로 테스트가 실행되면 휴먼 에러를 방지할 수 있다.
3. 스테이징 환경이 없을 때의 대안
스테이징 환경 구축이 어려운 소규모 팀에서 실용적인 방법이다.