테스트 전략 개요
Multi-SaaS Kit의 테스트 시스템과 전략을 소개합니다.
개요
Multi-SaaS Kit은 448개 테스트, 1,042개 assertion으로 구성된 견고한 테스트 시스템을 갖추고 있습니다. PHPUnit과 Pest 기반으로 작성되어 있으며, Core 모듈에 대해 서는 TDD 접근법을 적용합니다.
┌─────────────────────────────────────────────────────────────┐
│ 테스트 피라미드 │
│ │
│ /\ │
│ / \ E2E 테스트 (선택) │
│ /----\ │
│ / \ │
│ / \ Feature 테스트 (통합) │
│ /----------\ │
│ / \ │
│ / \ Unit 테스트 (단위) │
│ /----------------\ │
│ │
│ Unit : Feature = 7 : 3 비율 권장 │
└─────────────────────────────────────────────────────────────┘
테스트 스택
| 도구 | 역할 |
|---|---|
| PHPUnit | 테스트 프레임워크 |
| Pest | PHPUnit 래퍼 (간결한 문법) |
| SQLite In-Memory | 테스트 데이터베이스 |
| Factory | 테스트 데이터 생성 |
| RefreshDatabase | 테스트 간 DB 초기화 |
RefreshDatabase는 PHPUnit 실행 중 APP_ENV=testing에서 내부적으로 migrate:fresh를 호출할 수 있습니다. MSK의 DB 안전 가드는 이 테스트 러너 경로만 허용하므로, phpunit.xml과 tests/TestCase에서 운영 DB가 아닌 별도 테스트 DB 또는 SQLite in-memory를 사용하도록 반드시 고정해야 합니다.
테스트 현황
| 영역 | 테스트 수 | Assertions | 설명 |
|---|---|---|---|
| Core/Permission | 15 | 45 | 권한 시스템 (Level 0~6, ADR-058) |
| Core/Tenant | 17 | 51 | 테넌트 격리 |
| Core/Guardian | 72 | 216 | 후견인 관계 (Extension) |
| Core/Impersonate | 66 | 198 | 사용자 전환 (Extension) |
| Models | 81 | 243 | User, Tenant, Organization |
| Seeder | 39 | 117 | CoreSeeder 검증 |
| Filament | 41 | 123 | Admin Panel 기능 |
| Security | 8 | 24 | Cross-Tenant 보안 |
| Total | 448 | 1,042 | - |
테스트 분류
Unit 테스트
단일 클래스/메서드를 격리하여 테스트합니다.
tests/Unit/
├── Core/
│ ├── Permission/ # 권한 로직
│ ├── Tenant/ # 테넌트 스코프
│ ├── Guardian/ # 후견인 관계
│ └── Impersonate/ # 사용자 전환
├── Models/ # 모델 로직
└── Services/ # 서비스 로직
Feature 테스트
여러 컴포넌트를 통합하여 테스트합니다.
tests/Feature/
├── Core/
│ └── Security/ # Cross-Tenant 보안
├── Filament/ # Admin Panel
├── Http/ # API 엔드포인트
└── Seeder/ # CoreSeeder 검증
테스트 환경 설정
phpunit.xml
<phpunit>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>
TestCase 기본 설정
// tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// 테넌트 기능 비활성화 (Unit 테스트)
config(['core.tenant.enabled' => false]);
}
}
테스트 실행
전체 테스트
# 모든 테스트 실행
php artisan test
# 또는 Pest 직접 실행
./vendor/bin/pest
특정 테스트 실행
# 특정 파일
php artisan test tests/Unit/Core/Permission/HasLevelTest.php
# 특정 폴더
php artisan test tests/Unit/Core/
# 특정 메서드 (필터)
php artisan test --filter=test_user_can_check_level
# 그룹별 실행
php artisan test --group=permission
병렬 실행
# 병렬 테스트 (빠름)
php artisan test --parallel
# 프로세스 수 지정
php artisan test --parallel --processes=4
커버리지 확인
# HTML 리포트 생성
php artisan test --coverage-html reports/coverage
# 콘솔 요약
php artisan test --coverage
TDD 적용 원칙
Multi-SaaS Kit은 Core 모듈에 대해 **TDD (Test-Driven Development)**를 적용합니다.
TDD 필수 영역
| 영역 | 이유 |
|---|---|
| Core/Permission | 권한 오류 = 보안 사고 |
| Core/Tenant | 데이터 격리 = SaaS 핵심 |
| Core/Audit | 감사 로그 무결성 |
| 비즈니스 로직 | 계산, 상태 전이 |
| 보안 관련 | 인증, 인가 |
TDD 워크플로우
1. 실패하는 테스트 작성 (Red)
↓
2. 테스트를 통과하는 최소 코드 작성 (Green)
↓
3. 코드 리팩토링 (Refactor)
↓
4. 반복
테스트 헬퍼
CreatesTestTenant
테넌트 관련 테스트를 위한 Trait:
use Tests\Traits\CreatesTestTenant;
class MyTest extends TestCase
{
use CreatesTestTenant;
public function test_example(): void
{
// Tenant Admin으로 로그인
$user = $this->actAsTenantAdmin();
// 특정 테넌트 컨텍스트 설정
$this->setTenantContext($tenant);
}
}
Factory 사용
// 기본 사용자
$user = User::factory()->create();
// 특정 레벨 사용자
$admin = User::factory()->create(['level' => 0]);
// 테넌트와 함께
$tenant = Tenant::factory()
->has(User::factory()->count(3))
->create();
명명 규칙
테스트 파일
{테스트대상}Test.php
예:
HasLevelTest.php
TenantScopeTest.php
UserResourceTest.php
테스트 메서드
// 형식: test_{feature}_{scenario}_{expected_outcome}
test_user_can_check_subscription_status()
test_expired_subscription_returns_false()
test_tenant_user_cannot_see_other_tenant_data()
Pest 스타일
test('사용자는 구독 상태를 확인할 수 있다', function () {
// ...
});
it('만료된 구독은 false를 반환한다', function () {
// ...
});
멀티세션 격리 테스트 DB
여러 개발/AI 세션이 동시에 같은 프로젝트를 테스트할 때, 단일 공유 testing DB 의
migrate:fresh(전체 재생성)가 서로 충돌 합니다(relation already exists / hang). 이때
세션별 격리 DB(testing_<slug>)를 사용하면 경합 없이 병렬 테스트가 가능합니다.
사용법
# 1. 격리 DB 생성 (DB명 2번째 인자, 'testing_' prefix 필수)
ops/scripts/create-testing-db.sh exam.how testing_qgs
# 2. 격리 DB 로 테스트 (DB= 파라미터)
make test NAME=exam.how DB=testing_qgs
make test NAME=exam.how DB=testing_qgs ARGS="--filter='SomeTest'"
# 기본(단일 공유)은 그대로
make test NAME=exam.how # DB_DATABASE=testing
동작 원리
| 요소 | 동작 |
|---|---|
| 이름 규칙 | testing 또는 testing_* prefix 만 허용 |
| 운영 DB 보호 | TestCase + RLS 마이그레이션 가드가 str_starts_with($db, 'testing') 로 판정 → 운영 DB({project}_db)는 RefreshDatabase 자체가 차단됨 |
| DB 소유권 | 격리 DB OWNER 는 테스트가 연결하는 런타임 role 이어야 migrate:fresh(DDL) 가능 |
| 정리 | 사용 종료 후 DROP DATABASE testing_<slug> (운영자) |
CREATE DATABASE 는 superuser 권한이 필요합니다. 프로젝트 postgres 의 POSTGRES_USER
가 superuser 가 아니면 운영자가 postgres 로 직접 생성하세요.
CI/CD 통합
GitHub Actions 예시
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: xdebug
- name: Install Dependencies
run: composer install --no-interaction
- name: Run Tests
run: php artisan test --parallel
관련 문서
- TDD 적용 범위 - TDD 필수/권장 영역
- Unit 테스트 - 단위 테스트 작성법
- Feature 테스트 - 통합 테스트 작성법
- DB 안전 가드 - 테스트 DB 초기화와 파괴적 명령 차단
- Permission 모듈 - 권한 테스트 예시
- Tenant 모듈 - 테넌트 테스트 예시