코딩딩/Spring

테스트 코드, 어디까지 작성해야 할까?

전낙타 2024. 12. 11. 16:03

TDD를 하면서 많은 사람들이 빠지는 함정이 있다. 바로 "테스트 커버리지에 집착하는 것"이다. 테스트는 어디까지, 어떻게 작성해야 할까? 그리고 모든 메서드를 테스트해야 할까? 이런 질문에 답을 찾기 위해 이번 포스팅에서는 TDD의 핵심과 테스트 코드 작성의 방향성을 정리해보려고 한다.


너무나 명확한 코드는 테스트하지 말자

모든 코드를 테스트하려는 강박은 때로는 비효율적이다. 특히, 너무 단순하거나 명확한 코드라면 테스트를 작성하지 않아도 될 수 있다. 예를 들어, 단순한 getter/setter, 혹은 너무 기본적인 로직은 테스트 작성 없이도 충분히 신뢰할 수 있다.


테스트 작성에서 피해야 할 것

  1. 무의미한 테스트
    단순히 "테스트 코드 커버리지를 높이겠다"는 목적만으로 작성된 테스트는 아무런 가치를 주지 못한다.
    예: getter/setter를 테스트하는 코드.
  2. 느리고 쉽게 깨지는 테스트
    테스트는 신뢰성을 기반으로 한다. 하지만 테스트가 지나치게 느리거나 환경에 의존적이라 자주 깨진다면, 그 테스트는 오히려 독이 된다.
  3. 테스트가 불가능한 코드
    테스트를 작성할 수 없는 코드는 설계가 잘못된 경우가 많다.
    예: 강한 결합을 가진 코드, 내부 상태를 지나치게 많이 사용하는 코드.

이 세 가지 공통점은 잘못된 설계에서 기인한다. 이런 테스트는 테스트 작성이 아니라 코드 설계 자체를 개선해야 한다는 신호다.


테스트의 필요성

레거시란 무엇인가?

레거시는 단순히 오래된 코드가 아니다. 테스트 코드가 없는 코드를 레거시로 정의할 수 있다. 테스트 코드가 없는 시스템은 변경이 두렵고, 새로운 기능 추가가 고통스럽다.

테스트의 진짜 이유: Regression 방지

테스트는 정상적으로 동작하던 코드가 의도치 않게 망가지는 **회귀(Regression)**를 방지한다. 다음과 같은 이유로 테스트는 필수다:

  • 서비스 안정성 확보: 테스트 코드가 없으면 예상치 못한 상황에서 서비스가 뻗어버릴 수 있다.
  • 개발 부담 완화: 테스트는 좋은 아키텍처를 유도하고, 이를 통해 기능 개발과 유지보수 부담을 줄여준다.

TDD와 좋은 아키텍처: SOLID 원칙

테스트는 단순히 코드의 안정성을 확인하는 것에서 그치지 않는다. 테스트를 작성하면서 자연스럽게 좋은 아키텍처를 유도한다. 이를 SOLID 원칙과 연결해보면:

  1. 단일 책임 원칙 (SRP)
    • 테스트는 명확하고 간단해야 한다.
    • 테스트를 작성하면서 클래스나 메서드가 너무 복잡하다면, 책임을 분리할 시점이다.
  2. 개방-폐쇄 원칙 (OCP)
    • 테스트 코드와 프로덕션 코드를 나누면서 컴포넌트를 자유롭게 교체 가능한 설계를 하게 된다.
  3. 리스코프 치환 원칙 (LSP)
    • 테스트가 모든 케이스를 커버하고 있으므로, 서브 클래스가 부모 클래스의 역할을 제대로 수행하는지 테스트를 통해 검증할 수 있다.
  4. 인터페이스 분리 원칙 (ISP)
    • 테스트는 인터페이스 설계의 부족한 점을 드러낸다.
    • 불필요한 의존성을 확인하고 인터페이스를 간결하게 정리할 기회를 준다.
  5. 의존성 역전 원칙 (DIP)
    • 가짜 객체(Mock)를 사용하려면 의존성이 역전된 설계가 필요하다.
    • 자연스럽게 DI(Dependency Injection) 구조를 도입하게 된다.

테스트는 좋은 설계를 유도하고, 좋은 설계는 테스트를 더 쉽게 만들어준다. 이 둘은 서로 상호 보완적이다.


테스트의 분류: 대형, 중형, 소형

Google은 테스트를 크기에 따라 다음과 같이 나누는 것을 권장한다.

  1. 소형 테스트 (Small Test)
    • 단일 프로세스, 단일 스레드에서 동작.
    • DB 연결, Disk I/O, Blocking Call 등 외부 의존성을 배제.
    • 항상 빠르고 결과가 결정적이어야 한다.
    • 전체 테스트의 80% 이상을 차지해야 한다.
  2. 중형 테스트 (Medium Test)
    • 멀티 프로세스와 멀티 스레드 가능.
    • DB 연결과 같은 외부 시스템 사용 가능.
    • 느릴 수 있고, 멱등성이 보장되지 않을 수 있다.
    • 테스트의 약 15%.
  3. 대형 테스트 (Large Test)
    • 멀티 서버 환경에서 실행.
    • 테스트 환경 설정이 복잡하고, 실행 속도가 느림.
    • 테스트의 약 5%.

우리는 대부분의 시간을 소형 테스트 작성에 집중해야 한다. 중형과 대형 테스트는 꼭 필요한 경우에만 작성한다.


결론: 테스트의 본질에 집중하자

  • 테스트는 단순히 "코드가 잘 작동하는지"를 넘어, 설계 개선과 코드 품질 유지에 기여한다.
  • 모든 메서드를 테스트하려는 강박은 버리고, 테스트가 필요한 로직에 집중하자.
  • SOLID 원칙을 바탕으로 테스트 가능한 설계를 유도하고, 소형 테스트를 중심으로 효율적인 테스트 전략을 세우자.

TDD는 단순한 테스트 작성이 아니다. 설계 개선과 코드 품질 유지의 기반이 되는 개발 방식이다. 커버리지에 집착하지 말고, 테스트와 설계 사이의 균형을 찾아가는 데 집중하자.