단위 테스트에만 전적으로 의존하면 시스템이 전체적으로 잘 작동하는지 확신할 수 없다.
단위 테스트가 비지니스 로직을 확인하는 데 좋지만 비지니스 로직을 외부와 단절된 상태로 확인하는 것만으로는 충분하지 않다.
각 부분이 데이터베이스 등의 외부 시스템과 어떻게 통합되는지 확인해야 한다.
단위 테스트가 아닌 모든 테스트가 통합 테스트에 해당한다.
시스템이 프로세스 외부 의존성과 통합해 어떻게 작동하는지 검증한다.
단위 테스트와 통합 테스트 간의 균형을 유지 하는 것이 중요하다.
통합 테스트가 프로세스 외부 의존성에 직접 작동하면 느려지며 이러한 테스트는 유지비가 많이 든다.
통합 테스트는 코드를 더 많이 거치고 프로덕션 코드와 결합도가 낮아 회귀 방지와 리팩터링 내성이 우수하다.
단위 테스트로 가능한 많은 비지니스 시나리오의 예외 상황을 확인하고 통합 테스트는 주요 흐름과 단위 테스트가 다루지 몫한 기타 예외 상황을 다룬다.
테스트 대부분을 단위 테스트로 전환하면 유지비를 절감할 수 있다.
통합 테스트가 비지니스 시나리오당 하나 또는 두개 있으면 시스템 전체의 정확도를 보장할 수 있다.
통합 테스트는 단순한 애플리케이션에서도 가치가 있다.
다른 서브 시스템과 통합해 어떻게 작동하는지 확인하는 것이 중요하다.
통합 테스트에서 프로세스 외부 의존성과 상호 작용을 모두 확인하려면 가장 긴 주요 흐름을 선택하라.
이렇게 모든 상호 작용을 거치는 흐름이 없으면 외부 시스템과의 통신을 모두 확인하는 데 필요한 만큼 통합 테스트를 추가로 작성하라.
통합 테스트는 시스템이 프로세스 외부 의존성과 어떻게 통합하는지 검증한다.
프로세스 외부 의존성 두 가지
관리 의존성은 실제 인스턴스를 사용하고 비관리 의존성은 목으로 대체하라
해당 애플리케이션에만 노출되는 의존성은 관리 의존성
외부 애플리케이션에도 노출되면 비관리 의존성
실제 데이터베이스(관리 의존성)를 테스트 할 수 없으면
관리 의존성을 목으로 대체하면 통합 테스트의 리팩터링 내성과 회귀 방지가 떨어진다.
데이터베이스(관리 의존성)를 그대로 테스트 할 수 없으면 아예 작성하지 말고 도메인 모델의 단위 테스트에만 집중하라.
프로세스 외부 의존성을 두 가지로 분류해서 직접 테스트할 대상과 목으로 대체할 대상을 결정해야 한다.
ex)
애플리케이션 데이터베이스는 어떤 시스템도 접근할 수 없으므로 관리 의존성이다.
따라서 실제 인스턴스를 사용해야 한다.
반면 메시지 버스는 비관리 의존성이다. 메시지 버스 목적은 다른 시스템과의 통신을 가능하게 하는 것 뿐이다.
메시지 버스를 목으로 대체하고 컨트롤러와 목 간의 상호작용을 검증하게 된다.
통합 테스트 범주에 관리 의존성을 포함시키고 비관리 의존성을 목으로 대체하면 통합 테스트의 보호 수준이 엔드 투 엔드 테스트와 비슷해지므로 엔드 투 엔드 테스트를 생략할 수 있다.
하지만 배포 후 상태 점검을 위해 한 두 개 정도는 중요한 e2e테스트를 작성할 수 있다.
테스트가 가장 긴 주요 흐름을 거치게 해서 애플리케이션이 모든 프로세스 외부 의존성과 올바르게 통신할 수 있도록 한다.
많은 개발자가 외부 의존성을 위해 인터페이스를 도입한다.
인터페이스를 사용하는 일반적인 이유는
단일 구현을 위한 인터페이스는 추상화가 아니다.
해당 인터페이스를 구현하는 구체 클래스보다 결합도가 낮지 않다.
인터페이스가 진정으로 추상화되려면 구현이 적어도 두 가지 이상 있어야 한다.
단일 구현을 위한 인터페이스는 YAGNI를 위반한다.
YAGNI : 현재 필요하지 않은 기능에 시간을 들이지 말라
목을 사용하기 위해 인터페이스를 사용하는 것이다.
인터페이스가 없으면 테스트 대역을 만들 수 없으므로 테스트 대상 시스템과 프로세스 외부 의존성 간의 상호작용을 확인할 수 없다.
따라서 이러한 의존성을 목으로 처리할 필요가 없는 한 프로세스 외부 의존성에 대한 인터페이스를 두지 말라
비관리 의존성만 목으로 처리하므로 결국 비관리 의존성에 대해서만 인터페이스를 쓰라는 지침이 된다.
관리 의존성을 컨트롤러에 명시적으로 주입하고 해당 의존성을 구체 클래스로 사용하라.
진정한 추상화는 목과 상관 없이 인터페이스로 나타낼 수 있다.
그러나 목 대체 이외의 이유로 단일 구현을 위해 인터페이스를 도입하는 것은 YAGNI에 위배된다.
내부 의존성도 인터페이스 기반인 코드를 볼 수 있다.
만약 구현체가 하나만 있다면 좋지 않은 신호다.
프로세스 외부 의존성과 마찬가지로 도메인 클래스에 대해 단일 구현으로 인터페이스를 도입하는 이유는 목으로 처리하기 위한 것뿐이다.
그러나 프로세스 외부 의존성과 달리 도메인 클래스 간의 상호 작용을 확인해서는 안된다.
그렇게 하면 리팩터링 내성이 떨어진다.
도메인 모델 경계 명시하기
항상 도메인 모델을 코드베이스에서 명시적이고 잘 알려진 위치에 두도록 하라.
도메인 모델에 명시적 경계를 지정하면 코드의 해당 부분을 더 잘 보여주고 더 잘 설명할 수 있다.
도메인 클래스와 컨트롤러 사이의 명확한 경계로 단위 테스트와 통합 테스트의 차이점을 쉽게 구분할 수 있다.
애플리케이션 내 계층 줄이기
애플리케이션에 추상 계층이 너무 많으면 코드 베이스를 탐색하기 어렵고 숨은 로직을 이해하기 너무 어려워진다. 간접 계층은 코드를 추론하는 데 부정적인 영향을 미친다.
추상화가 지나치게 많으면 단위 테스트와 통합 테스트에 도움이 되지 않는다.
간접 계층이 많은 코드는 경계가 명확하지 않은 편이다. 그리고 각 계층을 따로 검증하는 경향이 훨씬 강하다. 이러한 경향으로 통합 테스트 가치가 떨어지고 각 테스트는 특정 계츠으이 코드만 실행하고 하위 계층은 목으로 처리한다. 최종 결과는 항상 똑같이 낮은 리팩터링 내성과 불충분한 회귀 방지이다.
가능한 계층을 적게 사용하라.
순환 의존성 제거하기
순환 의존성이 있으면 코드를 읽고 이해하기 어렵다. 해결책을 찾기 위한 출발점이 명확하지 않기 때문이다.
순환 의존성은 테스트를 방해한다. 클래스 그래프를 나눠서 동작 단위를 하나 분리하려면 인터페이스에 의존해 목으로 처리해야 하는 경우가 많으며 이는 도메인 모델을 테스트할 때 해서는 안 된다.
인터페이스 사용은 순환 의존성의 문제만 가린다. 컴파일 타임에는 제거할 수 있지만 런타임에는 제거할 수 없다.
테스트에서 다중 실행 구절 사용
테스트에서 여러번 검증하면 안된다.
각 실행을 고유의 테스트로 추출해 테스트를 나누는 것이 좋다.
로깅은 애플리케이션의 동작에 대한 중요한 정보를 생성한다.
그러나 로깅은 너무나 보편적이므로 테스트 노력을 더 들일 가치가 있는지는 분명하지 않다.
로깅이 애플리케이션의 식별할 수 있는 동작인가 아니면 구현 세부 사항인가.
로깅이 식별할 수 있는 동작이면 테스트 해야한다.
로그로 무언갈 하면 테스트 해야하고 디버깅 용도면 테스트 하면 안된다.
로깅에는 프로세스 외부 의존성이 있기 때문에 테스트에 관한 한 프로세스 외부 의존성에 영향을 주는 다른 기능과 동일한 규칙이 적용된다.
애플리케이션과 로그 저장소 간의 상호 작용을 검증하려면 목을 써야 한다.
도메인 모델에서는 진단 로깅을 절대 사용하지 않도록 하라.
도메인에서 컨트롤러를 안전하게 옮길 수 있다.
무언가 디버깅해야할때만 일시적으로 진단 로깅을 사용하라.
디버깅이 끝나면 제거하라