PHPUnit: @covers로 테스트 탐색과 커버리지 향상하기
PHP 웹 컨트롤러 테스트 시 테스트와 앱 코드 간 탐색이 골치 아픈 문제로 이어지곤 합니다. PHPUnit의 @covers 어노테이션과 속성이 이를 어떻게 해결하며, 커버리지를 희생하지 않고 개발을 간소화하는지 알아보겠습니다.
테스트와 코드 간 탐색의 어려움
HTTP 요청으로 컨트롤러를 테스트할 때 메서드를 직접 호출할 수 없어 테스트에서 코드로 바로 이동하기 어렵습니다. 예를 들어 /hello 엔드포인트 테스트는 GreetingController::hello()를 직접 참조하지 않습니다. 이로 인해 IDE에서 수동 검색을 해야 해 속도가 느려집니다.
다음은 컨트롤러와 테스트 예시입니다:
final class GreetingController extends AbstractController
{
#[Route('/hello')]
public function hello(): Response
{
return new Response($this->generate("Hello!"));
}
}
class GreetingControllerTest extends WebTestCase
{
public function test_hello(): void
{
$content = get('/hello');
assert_starts_with('Hello!', $content);
}
}
테스트와 코드를 연결하는 3가지 방법
개발자들은 탐색을 개선하기 위해 여러 방법을 씁니다.
1. 리플렉션으로 네이티브 구문 사용
문자열 경로 대신 리플렉션으로 메서드 호출처럼 사용: request(GreetingController::hello(...)). 단점: 테스트가 앱 코드에 의존해 변경 시 깨질 위험이 있습니다.
2. @see 어노테이션
PHPDoc으로 링크:
/**
* @see \App\Controller\GreetingController::hello()
*/
public function test_hello(): void { ... }
장점: IDE 지원. 단점: 전체 클래스 경로 필요, 일반 태그라 특별 처리 없음.
3. PHPUnit 전용 Covers
커버리지와 탐색을 위해 만들어진 어노테이션 또는 속성.
PHPUnit에서 Covers 어노테이션 사용하기
PHPUnit 12 이전에는 @coversDefaultClass와 @covers가 주를 이뤘습니다:
/**
* @coversDefaultClass \App\Controller\GreetingController
*/
class GreetingControllerTest extends WebTestCase
{
/**
* @covers ::hello
*/
public function test_hello(): void { ... }
}
장점:
- PhpStorm 최고 수준 지원: 자동완성, 탐색, 사용 감지.
- 테스트 생성용 커스텀 템플릿.
PhpStorm 템플릿 단점:
- PHPUnit 템플릿 2개로 제한.
- 이름이 헷갈림.
- 생성기 가끔 버그.
PHPUnit 12+에서 Covers 속성으로 전환
PHPUnit 12는 어노테이션을 속성으로 바꿨지만, 클래스에만 적용되고 메서드에는 안 됩니다.
기본 사용:
#[CoversClass('App\Controller\GreetingController')]
class GreetingControllerTest extends WebTestCase
{
public function test_hello(): void { ... }
}
메서드 세부 지정 없어 탐색 정밀도가 떨어집니다.
CoversMethod로 메서드 지정:
#[CoversMethod('App\Controller\GreetingController', 'hello')]
#[CoversMethod('App\Controller\GreetingController', 'bye')]
class GreetingControllerTest extends WebTestCase
{
public function test_hello(): void { ... }
public function test_bye(): void { ... }
}
단점: 여러 메서드 테스트 시 속성 과다.
최적 전략: 메서드당 전용 테스트 클래스
메서드별 테스트 클래스 분리:
#[CoversMethod('App\Controller\GreetingController', 'hello')]
class HelloTest extends WebTestCase
{
public function test_hello(): void { ... }
public function test_hello2(): void { ... }
}
이점:
- 정신적 부하 적음.
- 메서드별 covers 불필요.
- 깔끔한 설계 촉진: 컨트롤러당 엔드포인트 하나.
속성 템플릿은 어노테이션처럼 동작하나 생성 후 CoversMethod 수동 조정 필요.
IDE 통합과 향후 개선
PhpStorm은 이미 Ctrl+Shift+T로 테스트-클래스 이동 지원. 메서드 수준 탐색 곧 추가될 전망.
핵심 요약
- 탐색 향상을 위해 covers 사용, 커버리지 영향 주의.
- PHPUnit 12+ 속성은 클래스 전용이니 테스트 방식 조정.
- 유지보수 쉬운 메서드당 테스트 클래스 하나.
- IDE 템플릿으로 자동 covers 설정.
- PhpStorm 속성 지원 개선 업데이트 주시.
커버리지 제한: Covers의 최대 단점
Covers의 주 역할은 클래스/메서드 범위 커버리지 제한. CoversClass는 외부 코드 보고서 제외—엄격 프로젝트 좋지만, 대부분 개발자는 누락만 찾고 싶어합니다.
비활성화 우회 방법:
- 과도한 해킹: 테스트 전 태그 제거 스크립트.
- 실용적 해결: Composer로 phpunit/php-code-coverage 패치.
프로젝트 필요와 팀 선호에 맞게 선택.
— Editorial Team
아직 댓글이 없습니다.