박종훈 기술블로그

8장 통합 테스트를 하는 이유 (1)

8장 통합 테스트를 하는 이유 (1) 단위테스트 (블라디미르 코리코프)


8장에서 다루는 내용

  • 통합 테스트의 역할 이해
  • 테스트 피라미드의 개념 자세히 살펴보기
  • 가치 있는 통합 테스트 작성

단위 테스트는 비지니스 로직을 확인하는데 좋지만 비지니스 로직을 외부와 단절된 상태로 확인하는 것만으로는 시스템이 전체적으로 잘 작동하는지 확신할 수 없다. 각 부분이 데이터베이스나 메시지 버스 등의 외부 시스템과 어떻게 통합되는지 확인해야 한다.

이 장에서는

  • 통합 테스트의 역할, 즉 언제 적용해야 하는지와 일반적인 단위 테스트나 심지어 빠른 실패(Fail Fast) 원칙과 같은 다른 기법에 의존하는 것이 좋을지 등을 알아본다.
  • 또한 프로세스 외부 의존성 중에서 어느 것을 통합 테스트에서 그대로 사용하고 어느 것을 목으로 대체할지에 대해 판단하는 방법에 대해 알아본다.
  • 도메인 모델 경계를 명시하고 애플리케이션의 계층 수를 줄이고 순환 의존성을 제거하는 등 코드베이스의 상태를 개선하는 데 도움이 되는 통합 테스트 모범 사례에 대해서도 알아본다.
  • 마지막으로, 구현이 하나뿐인 인터페이스를 가끔 사용해야 하는 이유와 로깅 기능을 언제 어떻게 테스트하는지 알아본다.

8.1 통합 테스트는 무엇인가?

통합 테스트는 테스트 승쉬트에서 중요한 역할을 하며, 단위 테스트 개수와 통합 테스트 개수의 균형을 맞추는 것도 중요하다.

8.1.1 통합 테스트의 역할

단위 테스트는 다음 세 가지 요구 사항을 충족하는 테스트다.

  • 단일 동작 단위를 검증하고,
  • 빠르게 수행하고,
  • 다른 테스트와 별도로 처리한다.

이 세 가지 요구 사항 중 하나라도 충족하지 못하는 테스트는 통합 테스트 범주에 속한다. 단위 테스트가 아닌 모든 테스트는 통합 테스트에 해당한다.

실제로 통합 테스트는 대부분 시스템이 프로세스 외부 의존성과 통합해 어떻게 작동하는지를 검증한다. 다시 말해, 통합 테스트는 컨트롤러 사분면 (우측 하단)에 속하는 코드를 다룬다.

네가지 코드 유형

프로세스 외부 의존성을 목으로 대체한다면 컨트롤러 사분면을 단위 테스트로 처리할 수 있다. 그러나 대부분의 애플리케이션은 목으로 대체할 수 없는 프로세스 외부 의존성이 있다. 예로 들면 데이터베이스가 있다.

간단한 코드와 지나치게 복잡한 코드는 전혀 테스트해서는 안 된다. 간단한 코드는 노력을 들일 만한 가치가 없고, 복잡한 코드는 알고리즘과 컨트롤러로 리팩터링해야 한다.

8.1.2 다시 보는 테스트 피라미드

testing pyramid

통합 테스트는 단위 테스트에 비해 유지비가 많이 든다. 반면 회귀 방지가 단위 테스트보다 우수하다. 또한 리팩터링 내성도 우수하다.

단위 테스트로 가능한 한 많이 비즈니스 시나리오의 예외 상황을 확인하고, 통합 테스트는 주요 흐름(happy path)과 단위 테스트가 다루지 못하는 기타 예외 상황(edge case)을 다룬다.

주요 흐름은 시나리오의 성공적인 실행이다. 예외 상황은 비즈니스 시나리오 수행 중 오류가 발생하는 경우다.

엔드투엔드 테스트 (E2E 테스트) 는 통합 테스트의 하위 집합이다.

통합 테스트는 단순한 애플리케이션에서도 가치가 있다. 코드가 얼마나 간단한지보다 다른 서브 시스템과 통합해 어떻게 작동하는지 확인하는 것이 더 중요하다.

8.1.3 통합 테스트와 빠른 실패

통합 테스트에서 프로세스 외부 의존성과의 상호 작용을 모두 확인하려면 가장 긴 주요 흐름을 선택하라. 이렇게 모든 상호 작용을 거치는 흐름이 없으면, 외부 시스템과의 통신을 모두 확인하는 데 필요한 만큼 통합 테스트를 추가로 작성하라.

[팁] 좋지 않은 테스트를 작성하는 것보다는 테스트를 작성하지 않는 것이 좋다. 가치가 별로 없는 테스트는 좋지 않은 테스트다.

빠른 실패 원칙

빠른 신패 원칙은 예기치 않은 오류가 발생하자마자 현재 연산을 중단하는 것을 의미한다.

이 원칙은 다음을 통해 애플리케이션의 안정성을 높인다.

  • 피드백 루프 단축 : 버그를 빨리 발견할수록 더 쉽게 해결할 수 있다. 이미 운영 환경으로 넘어온 버그는 개발 중에 발견된 버그보다 수정 비용이 훨씬 더 크다.
  • 지속성 상태 보호: 버그는 애플리케이션 상태를 손상시킨다. 손상된 상태가 데이터베이스로 침투하면, 고치기가 훨씬 어려워진다. 빨리 실패하면 손상이 확산되는 것을 막을 수 있다.

보통 예외를 던져서 현재 연산을 중지한다. 예외는 그 의미가 빠른 실패 원칙에 완벽히 부합되기 때문이다. 예외는 프로그램 흐름을 중단하고 실행 스택에서 가장 높은 레벨로 올라간 후 로그를 남기고 작업을 종료하거나 재시작할 수 있다.

전제 조건은 빠른 실패 원칙의 예다. 전제 조건이 실패하면 애플리케이션 상태에 대해 가정이 잘못된 것을 의미하는데, 이는 항상 버그에 해당한다. 또 다른 예는 설정 파일에서 데이터를 읽는 것이다. 설정 파일의 데이터가 불완전하거나 잘못된 경우 예외가 발생하도록 판독 로직을 구성할 수 있다. 이 로직을 애플리케이션 시작 부근에 둬서 문제가 있으면 애플리케이션이 시작하지 않도록 할 수 있다.