Fivefy 프로젝트/트러블슈팅

커버리지 100% 대신 의미 있는 테스트를 선택한 이유

sudaruuu 2026. 4. 15. 23:20

문제 상황

Playback 도메인 테스트 코드를 작성한 뒤 JaCoCo로 커버리지를 확인했을 때,

대부분의 로직은 테스트되었지만 서비스 계층 일부 분기가 실행되지 않아 커버리지 100%에 도달하지 못했다.

 

특히 아래 두 부분이 미실행으로 남았다.

  • play() 메서드에서 동일 곡 재생 중 요청 처리 분기
  • handlePlay() 메서드 마지막 INVALID_PLAYBACK_STATE 예외 분기

커버리지 100%를 달성하기 위해 추가 테스트 작성이 필요한 상황이었다.


원인 분석

코드 흐름을 다시 따라가며 분석해보니,

남아 있는 분기들은 단순히 테스트를 덜 작성해서가 아니라 현재 구조상 정상적인 흐름으로는 도달하기 어려운 코드였다.

1. 동일 곡 재생 중 분기

play()에서는 현재 세션에서 PLAYING 상태의 playback을 조회한 뒤,

  • 동일한 곡이면 → 중복 재생 요청으로 간주하여 예외 발생
  • 다른 곡이면 → 기존 playback 종료

하도록 구성되어 있다.

 

즉 이 분기는 실제 비즈니스 규칙인 “같은 곡을 중복 재생할 수 없다”를 명확하게 표현한 로직이다.


2. handlePlay() 마지막 예외 분기

handlePlay()는 다음 상태만 처리한다.

  • PAUSED → resume
  • STOPPED, COMPLETED, SKIPPED → 새 playback 생성

그 외 상태는 마지막에서 예외를 던진다.

throw new BusinessException(PlaybackErrorCode.INVALID_PLAYBACK_STATE);

 

하지만 현재 play() 흐름상 이 메서드로 전달되는 객체는 이미 위 상태들로 제한되어 있기 때문에, 해당 분기는 사실상 방어 코드에 가깝다.

 

즉 이 코드를 실행시키기 위해 테스트를 작성하려면 실제 흐름과 맞지 않는 비자연스러운 상황을 만들어야 했다.

따라서 이 분기는 테스트를 위한 코드가 아니라, 시스템 안정성을 위한 방어 코드로 판단하였다.


고민

여기서 두 가지 선택지가 있었다.

1. 커버리지 100% 달성

  • 모든 분기를 억지로라도 실행시키는 테스트 작성
  • 테스트가 구현 세부사항에 강하게 의존

2. 의미 있는 테스트 유지

  • 실제 발생 가능한 흐름 중심 테스트 유지
  • 방어 코드는 그대로 두고 커버리지 일부 포기

해결 방향

이번에는 커버리지 100% 달성보다 코드의 의미와 테스트의 자연스러움을 더 중요하게 판단했다.

 

따라서 다음과 같은 방향으로 정리하였다.

  • 중복 재생 요청 시 즉시 예외 발생하도록 로직 명확화
  • 상태 전이 흐름(pause, stop, skip)을 기준으로 테스트 구성
  • 상태 변경 시 save()를 명시적으로 호출하여 코드 의도 명확화
  • 방어 코드 분기는 유지하되, 억지 테스트는 작성하지 않음

결과

  • 핵심 재생 흐름(play, pause, stop, skip)에 대한 테스트 검증 완료
  • 중복 재생 방지 및 상태 전이 로직 정상 동작 확인
  • 주요 예외 케이스(404, 409 등) 테스트로 검증

결과적으로 서비스 커버리지는 100%에 도달하지 않았지만,

실제 비즈니스 흐름을 충분히 검증할 수 있는 테스트는 확보된 상태라고 판단하였다.


느낀 점

이번 경험을 통해 커버리지 100%라는 숫자 자체가 항상 좋은 것은 아니라는 것을 느꼈다.

특히 방어 코드나 구조적으로 도달하기 어려운 분기까지 억지로 테스트하려 하면, 오히려 테스트가 복잡해지고 코드 구조가 왜곡될 수 있다는 점을 알게 되었다. 앞으로는 커버리지 수치를 참고 지표로 활용하되, 그보다 중요한 것은 “이 테스트가 실제 비즈니스 흐름을 잘 설명하고 있는가”라는 기준이라고 생각한다.

테스트는 단순히 수치를 채우기 위한 도구가 아니라, 코드의 의도를 설명하기 위한 도구라는 것을 다시 한번 느낄 수 있었다.