‘블라디미르 코리코프’ 저 ‘단위 테스트’ 책을 읽고 정리한 내용
📚 ‘단위 테스트’의 정의
- 작은 코드 조각(단위)을 검증하고
- 빠르게 수행하고
- 격리된 방식으로 처리하는 자동화된 테스트
단위 테스트의 정의에는 많은 뉘앙쓰가 존재한다. 해석의 차이가 생겼고 단위 테스트에 접근하는 방법이 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)객체 역시 교체 대상이 아니다.
📚 고전파와 런던파의 비교
런던파의 장점
- 입자성이 좋다. 테스트가 세밀해서 한 번에 한 클래스만 확인한다.
- 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다.
- 테스트가 실패하면 어떤 기능이 실패했는지 보다 더 빠르고 확실하게 알 수 있다.
특히 실패 원인을 빠르고 확실하게 알 수 있다는 점이 가장 큰 장점이다. 고전파 테스트도 정기적으로 실행한다면 버그의 원인을 쉽게 빠르게 찾아낼 수 있지만, 현재 테스트를 실행하는 주체가 코드 작성자가 아닌 외부인이라면 버그의 원인을 찾아내는 데에 런던파보다 시간이 더 소요될 것은 분명하다.
또, 런던파 단위 테스트들로 구성되었을 때 하나의 프로덕션 코드 수정 후 전체 런던파 테스트가 실패한다면, 전체 시스템이 그 객체(클래스)에 지나치게 의존한다는 것을 인지하게 해준다는 장점이 존재한다.
런던파의 단점
“우리집 강아지를 부르면, 바로 나에게 온다.” - 고전파
“우리집 강아지를 부르면 먼저 왼쪽 앞다리를 움직이고, 이어서 오른쪽 앞다리를 움직이고, 머리를 돌리고, 꼬리를 흔글기 시작한다…” - 런던파
무엇이 더 의미 있는 이야기인가? 또 어떤 이야기인가? 런던파의 설명에서 강아지는 나에게로 오는 것인가? 아니면 도망가는 것인가?
또 객체 그래프의 복잡도를 줄일 수 있다는 말에는 큰 오류가 존재한다. 객체 그래프가 복잡하게 그려진다면 런던파 방식을 이용하기 이전에 객체 협력간 복잡도를 줄이는 것이 선행되어야 할 것이다. 런던파 단위 테스트는 객체 복잡도 문제를 감출뿐, 근본적인 원인을 해결하진 못한다.
결론
- 런던파는 얼핏 보기에 좋아보인다. 그러나 많은 문제점을 가지고 있다.
- ‘테스트는 코드 단위가 아니라 동작 단위를 검증해야 한다.’
- 테스트 대역을 사용하는 것은 문제를 해결하기보다 숨기는데 도움이 된다.
- 때문에 책의 저자는 고전파를 선호한다.
댓글남기기