‘블라디미르 코리코프’ 저 ‘단위 테스트’ 책을 읽고 정리한 내용

📚 ‘단위 테스트’의 정의

  • 작은 코드 조각(단위)을 검증하고
  • 빠르게 수행하고
  • 격리된 방식으로 처리하는 자동화된 테스트

단위 테스트의 정의에는 많은 뉘앙쓰가 존재한다. 해석의 차이가 생겼고 단위 테스트에 접근하는 방법이 2가지 뚜렷한 견해로 나뉘었다. 이러한 2가지 견해는 각각 고전파(classical school)런던파(london school)로 알려져있다.


📚 격리 문제에 대한 런던파의 접근

코드 조각(단위)을 격리된 방식으로 검증한다는 것은 무엇을 의미하는가? 런던파에서는 테스트 대상 시스템을 협력자(collaborator)에게서 격리하는 것을 일컫는다.

즉 하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역(test double)으로 대체해야한다. 이런 식으로 동작을 외부 영향과 분리해서 테스트 대상 클래스에만 집중할 수 있다.

즉 런던파는 모든 의존 객체를 대역으로 대체한다.

런던파의 특징

  • 이 방법의 명확한 장점은 테스트가 실패하면 코드베이스의 어느 부분이 고장 났는지 확실히 알 수 있다.
  • 아주 빠르고 정확하게 원인 진단이 가능하다.
  • 또 다른 이점은 객체 그래프를 분할할 수 있다. 의존성이 깊고 넓을 수록 그래프가 상당히 복잡하고, 심지어 순환이 발생할 수도 있는데 의존 객체를 대역함으로써 문제를 해당 문제를 해결할 수도 있다.
  • 또 테스트를 작성할 때 테스트 스위트가 굉장히 명확하고 깔끔하다. 하나의 기능당 온전히 독립된 하나의 테스트!
  • 대역 라이브러리(Mockito 등)을 사용하면 의존성 분리뿐만 아니라 메서드 호출 회수를 카운팅, 검증 할 수도 있다.
  • 고전파 방식의 경우 객체 그래프가 깊으면 의존에 의존에 의존에… 필요한 객체를 일일히 추가해주어야한다. 즉 객체 그래프를 다시 한 번 그리는 것이다.

그러나 이 부분은 문제를 떠올려봐야한다. 애초에 단위 테스트의 갖아 큰 이점은 테스트를 신뢰하고 리팩토링하는 것이라고 생각한다. 객체 그래프가 복잡하면 애초에 연관관계가 망가진게 아닐까 의심해봐야한다.


📚 격리 문제에 대한 고전파의 접근

고전파는 단위 테스트를 작성할 때 객체간 협력이 일어나기 때문에 런던파에 비해 작은 코드 조각(단위)가 큰 편이다. 때에 따라 한 번에 몇 개의 클래스(객체)를 테스트할 수도 있다. 그러나 일반적으로는 한 번에 한 클래스로 테스트하는 지침을 따르려고 노력해야 한다.

고전파가 클래스간의 격리를 꼭 해야하는건 아니지만, 테스트 간 서로의 결과에 영향을 미쳐서는 안된다. 데이터베이스, 파일 시스템 등 프로세스 외부 의존성을 공유하면서 서로의 결과에 영향을 주면 단위 단위 테스트라고 할 수 없다.

고전파의 특징

  • 고전파의 스타일은 테스트 협력자를 대체하지 않고 운영용 인스턴스를 사용한다.
    • 이 때문에 자연스럽게 협력자 인스턴스도 함께 검증이 진행된다.
    • 그러나 테스트에 에러가 발생하면 테스트의 주체가 되는 인스턴스와 협력 인스턴스 중 어떤 부분이 문제인지 확인이 어렵다.
    • 서로 격리되지 않아 영향을 끼치기 때문이다.


📚 공유 의존성, 비공개 의존성, 프로세스 외부 의존성

  • 공유 의존성(shared dependency)
    • 테스트 간에 공유되고 서로 결과에 영향을 미칠 수 있는 수단
    • 전형적인 예시는 static mutable field, 데이터베이스 등
  • 비공개 의존성(private dependency)
    • 테스트간 서로 공유하지 않는 의존성
  • 프로세스 외부 의존성(out of process dependency)
    • 애플리케이션 실행 프로세스 외부에서 실행되는 의존성
    • 아직 메모리에 없는 데이터에 대한 프록시.
    • 일반적인 데이터베이스는 프로세스 외부 의존성이면서 동시에 공유 의존성이 될 수 있다.
    • 반대로 모든 테스트마다 데이터베이스를 초기화 시킨다면 프로세스 외부 의존성이면서 비공개 의존성이 될 수 있다.

테스트 구성을 어떻게 하느냐에 따라 동일한 매개체의 성격(의존성)이 달라질 수 있다. 예를 들어 프로덕션에서는 항상 싱글턴을 유지해야하는 클래스도, 단위 테스트에서는 매 번 새로 만들어지게 구성할 수 있다. 그럴 경우 테스트 간 서로 영향을 주지 않으므로, 비공개 의존성으로 볼 수 있다.

그러나 외부 요인(데이터베이스, 파일 시스템) 등은 불가능한 경우가 많다. 이럴 경우 테스트간 간섭이 발생하지 않도록 주의해야 한다.


📚 공유 의존성과 휘발성 의존성

  • 휘발성 의존성(volatile dependency)
    • 개발자가 의도적으로 준비해야하는 것
      • 개발자 컴퓨터에 기본적으로 설치된 환경 외에 런타임 환경의 설정 및 구성
      • 데이터베이스, API 서비스 등
    • 비결정적 동작(nondeterministic behavior)을 포함
      • 난수 생성기 또는 현재 날짜와 시간 반환 클래스 등
      • 각 호출마다 각기다른 결과를 응답하므로 비결정적이라 부른다.

예를 들어 데이터베이스는 공유 의존성이자 휘발성 의존성이다. 직접 데이터를 삽입하자마자 테스트를 진행하는게 아닐 경우 테이블에 어떤 값이 있을지 예측이 불가능하며 테이블의 값이 추가되거나 수정되면 매 번 결과가 달라진다. 그러나 파일 시스템은 모든 개발자들의 컴퓨터에 설치되어 있으며 대부분의 경우 결정적으로 동작하므로 휘발성 의존성은 아니다.

데이터베이스나 파일 시스템 등의 공유 의존성에 대한 호출은 비공개 의존성에 대한 호출보다 더 오래 걸린다(연결 시간). 단위 테스트는 빠르게 실행되어야 한다는 필요성이 있으므로, 외부와 연결을 통한 공유 의존성을 가진 테스트는 보통 통합 테스트로 바라보는 편이다.

이러한 시야로 단위 테스트를 바라볼 때 단위가 꼭 클래스 하나에 국한될 필요는 없다. 공유 의존성만 없다면 여러 클래스를 묶어서 진행하는 테스트도 단위 테스트라고 볼 수 있다. (테스트간 영향을 주는 요소가 없다는 가정 하에) 또한 불변(또는 값 객체 - VO)객체 역시 교체 대상이 아니다.


📚 고전파와 런던파의 비교

런던파의 장점

  • 입자성이 좋다. 테스트가 세밀해서 한 번에 한 클래스만 확인한다.
  • 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다.
  • 테스트가 실패하면 어떤 기능이 실패했는지 보다 더 빠르고 확실하게 알 수 있다.

특히 실패 원인을 빠르고 확실하게 알 수 있다는 점이 가장 큰 장점이다. 고전파 테스트도 정기적으로 실행한다면 버그의 원인을 쉽게 빠르게 찾아낼 수 있지만, 현재 테스트를 실행하는 주체가 코드 작성자가 아닌 외부인이라면 버그의 원인을 찾아내는 데에 런던파보다 시간이 더 소요될 것은 분명하다.

또, 런던파 단위 테스트들로 구성되었을 때 하나의 프로덕션 코드 수정 후 전체 런던파 테스트가 실패한다면, 전체 시스템이 그 객체(클래스)에 지나치게 의존한다는 것을 인지하게 해준다는 장점이 존재한다.

런던파의 단점

“우리집 강아지를 부르면, 바로 나에게 온다.” - 고전파
“우리집 강아지를 부르면 먼저 왼쪽 앞다리를 움직이고, 이어서 오른쪽 앞다리를 움직이고, 머리를 돌리고, 꼬리를 흔글기 시작한다…” - 런던파

무엇이 더 의미 있는 이야기인가? 또 어떤 이야기인가? 런던파의 설명에서 강아지는 나에게로 오는 것인가? 아니면 도망가는 것인가?

또 객체 그래프의 복잡도를 줄일 수 있다는 말에는 큰 오류가 존재한다. 객체 그래프가 복잡하게 그려진다면 런던파 방식을 이용하기 이전에 객체 협력간 복잡도를 줄이는 것이 선행되어야 할 것이다. 런던파 단위 테스트는 객체 복잡도 문제를 감출뿐, 근본적인 원인을 해결하진 못한다.

결론

  • 런던파는 얼핏 보기에 좋아보인다. 그러나 많은 문제점을 가지고 있다.
    • ‘테스트는 코드 단위가 아니라 동작 단위를 검증해야 한다.’
    • 테스트 대역을 사용하는 것은 문제를 해결하기보다 숨기는데 도움이 된다.
    • 때문에 책의 저자는 고전파를 선호한다.

댓글남기기