테스트 코드를 작성하다가 입력값만 살짝 바꿔가며 유사한 검증 메서드를 수십 번 반복 작성해 본 적이 있을 것이다.
다만 대부분의 개발자들은 단순 복사 붙여넣기로 테스트 케이스를 늘리다가 유지보수가 불가능할 정도로 파일이 무거워지는 문제의 원인을 모르는 경우가 많다.
이번에는 PHPUnit의 DataProvider가 정확히 뭔지, 왜 필요한지, 어떻게 적용하여 반복 코드를 깔끔하게 제거하는지 완벽하게 정리해서 소개하겠다.

 

1. PHPUnit DataProvider의 개념과 필요성

단위 테스트(Unit Test)를 작성할 때 동일한 메서드에 대해 다양한 예외 조건과 입력값을 검증하는 것은 필수적이다. 이메일 형식 검증, 비밀번호 정책 검사, 결제 금액 계산 등은 대표적으로 수많은 조건 조합이 발생하는 영역이다.
이때 각 조건마다 별도의 test* 메서드를 생성하면 코드 중복이 늘어나고, 로직 수정 시 모든 테스트 메서드를 일일이 고쳐야 하는 번거로움이 발생한다.

PHPUnit의 DataProvider(데이터 공급자) 기능은 테스트 로직과 테스트 데이터를 완전 분리하는 매개변수화 테스트(Parameterized Test)를 지원한다. 하나의 테스트 메서드에 배열 형태로 정의된 여러 데이터를 순차적으로 주입하여 실행하므로 코드 줄 수를 획기적으로 줄여준다.

구분단순 반복 작성 방식DataProvider 활용 방식
코드 중복성테스트 로직이 케이스마다 복사되어 중복 발생테스트 로직은 1개로 유지, 데이터만 배열로 공급
가독성 및 유지보수테스트 케이스 변경 시 모든 메서드 수정 필요데이터 공급용 배열에 요소만 추가/수정하면 완료
실패 케이스 식별메서드 이름만으로 실패 위치 파악 가능배열 키 이름을 통해 정확한 데이터 세트 식별 가능

 

2. DataProvider 기본 작성법과 연결 규칙

DataProvider를 구성하려면 데이터를 반환하는 정적(static) 메서드를 정의하고, 테스트 메서드 상단에 속성(Attribute) 또는 주석(Annotation)을 통해 연결해야 한다. PHPUnit 10 버전부터는 PHP 8 속성 방식인 #[DataProvider('메서드명')] 사용이 권장된다.

데이터 공급자 메서드는 반드시 static 메서드로 선언되어야 하며, 배열이나 Iterator를 반환해야 한다. 각 배열의 요소는 테스트 메서드의 파라미터 순서와 1:1로 일치하게 매핑된다.

 

3. 실전 예제: 회원 가입 이메일 유효성 검증

사용자 입력을 검증하는 클래스를 테스트하는 상황을 예로 들어보겠다. 경계값과 예외 케이스를 함께 테스트할 때 두 방식의 차이를 비교해 본다.

 

✗ 잘못된 방식: 입력값마다 독립된 테스트 메서드 연쇄 작성

아래 형태는 조건이 추가될 때마다 유사한 테스트 코드를 통째로 복사해서 작성하는 방식이다. 검증 대상 클래스의 호출 구문이 바뀔 경우 모든 메서드를 수정해야 한다.

<?php

use PHPUnit\Framework\TestCase;

class UserValidatorTest extends TestCase
{
    public function testEmptyEmailReturnsFalse(): void
    {
        $validator = new UserValidator();
        $result = $validator->validateEmail('');
        $this->assertFalse($result);
    }

    public function testInvalidEmailFormatReturnsFalse(): void
    {
        $validator = new UserValidator();
        $result = $validator->validateEmail('user-at-domain.com');
        $this->assertFalse($result);
    }

    public function testNoDomainEmailReturnsFalse(): void
    {
        $validator = new UserValidator();
        $result = $validator->validateEmail('user@');
        $this->assertFalse($result);
    }

    public function testValidEmailReturnsTrue(): void
    {
        $validator = new UserValidator();
        $result = $validator->validateEmail('developer@redinfo.co.kr');
        $this->assertTrue($result);
    }
}

 

✓ 올바른 방식: DataProvider로 입력 데이터와 기댓값 분리

데이터 공급 메서드를 생성하고 테스트 메서드에는 검증 로직 하나만 남겨둔다. 배열의 키 값으로 케이스 설명을 적어두면 테스트 결과 리포트에서 실패한 이유를 직관적으로 확인할 수 있다.

<?php

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;

class UserValidatorTest extends TestCase
{
    /**
     * 이메일 검증용 테스트 데이터 공급자
     * @return array<string, array{0: string, 1: bool}>
     */
    public static function emailDataProvider(): array
    {
        return [
            '빈 문자열 입력' => ['', false],
            '골뱅이 기호 누락' => ['invalid-email-format', false],
            '도메인 입력 누락' => ['user@', false],
            '정상 이메일 입력' => ['developer@redinfo.co.kr', true],
            '서브도메인 이메일' => ['admin@sub.redinfo.co.kr', true],
        ];
    }

    #[DataProvider('emailDataProvider')]
    public function testValidateEmail(string $email, bool $expected): void
    {
        $validator = new UserValidator();
        $actual = $validator->validateEmail($email);
        
        $this->assertSame($expected, $actual);
    }
}

 

테스트 실행 결과 확인

터미널에서 실행하면 데이터 공급자의 키 이름이 테스트 케이스 설명으로 출력되어 어떤 입력에서 결과가 달라졌는지 명확하게 식별된다.

$ vendor/bin/phpunit --testdox Tests/UserValidatorTest.php

User Validator (UserValidatorTest)
 ✔ Validate email with '빈 문자열 입력'
 ✔ Validate email with '골뱅이 기호 누락'
 ✔ Validate email with '도메인 입력 누락'
 ✔ Validate email with '정상 이메일 입력'
 ✔ Validate email with '서브도메인 이메일'

OK (5 tests, 5 assertions)

 

4. 실무에서 자주 하는 실수와 주의사항

DataProvider를 다룰 때 자주 범하는 대표적인 문제들과 올바른 접근 방식을 정리했다.

✗ 잘못된 것: DataProvider 메서드를 non-static으로 선언하거나 인스턴스 변수에 의존하는 것.
✓ 올바른 것: PHPUnit 실행 시점 최적화를 위해 DataProvider는 순수 정적(static) 데이터만 반환하도록 구현해야 한다.

✗ 잘못된 것: 배열 키를 단순 숫자 인덱스(0, 1, 2...)로 방치하는 것.
✓ 올바른 것: 연관 배열 키(예: '비밀번호 8자리 미만')를 지정해야 CI/CD 빌드 실패 로그에서 에러 원인을 즉시 알아챌 수 있다.

✗ 잘못된 것: DataProvider 내부에서 DB 조회나 외부 API 호출 같은 무거운 I/O 작업을 수행하는 것.
✓ 올바른 것: DataProvider는 테스트 집계 단계에서 미리 실행되므로, 순수 데이터 객체(Mock 또는 DTO) 형태만 생성해서 넘겨주어야 전체 테스트 속도가 떨어지지 않는다.

 

5. 핵심 정리 및 다음 단계

PHPUnit DataProvider는 테스트 코드의 중복성을 제거하고 다양한 경계 조건을 효율적으로 관리하게 해주는 필수 도구다. 작은 테스트 구조의 최적화 습관이 모여서 애플리케이션의 높은 회귀 테스트 안정성과 깨끗한 테스트 코드베이스를 만든다는 점을 잊지 말자. 이 글의 실전 예제 코드를 참고해 기존에 반복해서 작성했던 테스트 메서드들을 DataProvider 기반으로 리팩토링하면, 더 높은 커버리지와 깔끔한 테스트 수트를 완성할 수 있을 것이다.