2022.04.27
비관리 의존성에만 목을 사용하게끔 제한하는 것이 중요하지만, 이는 목의 가치를 극대화하기 위한 첫번째 단계일 뿐이다.
목을 사용할 때 항상 다음 지침을 따르자. 세스템 끝에서 비관리 의존성과의 상호 작용을 검증하라.
비관릐 의존성과 통신하는 마지막 타입을 목으로 처리하면 통합 테스트가 거치는 클래스의 수가 증가하므로 보호가 향상된다. 이 지침은 EventDispatcher
를 목으로 처리하고 싶지 않은 이유이기도 하다. 시스테템의 끝에서부터의 거리가 IMessageBus
에 비해 더 멀다.
IBus
는 시스템 끝에 있다. IMessageBus
는 컨트롤러와 메시지 버스 사이의 타입 사슬에서 중간 고리일 뿐이다. MessageBus
대신 IBus
를 목으로 처리하면 회귀 방지가 좋아진다.
외부 시스템은 애플리케이션으로부터 텍스트 메시지를 수신하고, MessageBus
와 같은 클래스를 호출하지 않는다. 실제로 텍스트 메시지는 외부에서 식별할 수 있는 유일한 부작용이다. 이러한 메시지를 생성하는 데 참여하는 클래스는 단지 구현 세부 사항일 뿐이다.
따라서 시스템 끝에서 상호 작용을 확인하면 회귀 방지가 좋아질뿐만 아니라 리팩터링 내성도 향상된다.
또한, 이러한 이러한 코드는 코드베이스와의 결합도가 낮기 때문에 낮은 수준의 리팩터링에도 영향을 많이 받지 않는다.
스파이는 목과 같은 목적을 수행하는 테스트 대역이다. 스파이는 수동으로 작성하는 반면에 목은 목 프레임워크의 도움을 받아 생성한다는 것이 유일한 차이점이다.
[Fact]
public void Changing_email_from_corporate_to_non_corporate()
{
var busSpy = new BusSpy();
var messageBus = new MessageBus(busSpy);
var loggerMock = new Mock<IDomainLogger>();
var sut = new UserController(db, messageBus, loggerMock.Object);
busSpy.ShouldlSendNumberOfMessages(1)
.WithEmailChangedMessage(user.UserId, "new@gmail.com");
}
BusSpy
가 제공하는 플루언트 인터페이스(fluent interface)덕분에 이제 메시지 버스와의 상호 작용을 검증하는 것이 간결해졌고 표현력도 생겼다.
목이 통합 테스트만을 위한 것이며 단위 테스트에서 목을 사용하면 안 된다는 지침은 기본 원칙인 비지니스 로직과 오케스트레이션의 분리에서 비롯된다.
코드가 복잡하거나 프로세스 외부 의존성과 통신할 수 있지만, 둘 다는 아니다.
도메인 모델에 대한 테스트는 단위 테스트 범주에 속하며, 컨트롤러는 다루는 테스트는 통합 테스트다. 목은 비관리 의존성에만 해당하며 컨트롤러만 이러한 의존성을 처리하는 코드이기 때문에 통합 테스트에서 컨트롤러를 테스트할 때만 목을 적용해야 한다.
동작 단위를 구현하는 데 필요한 코드의 양은 관계가 없다. 단일 클래스부터 여러 클래스에 이르기까지 다양하게 걸쳐 있을 수 있고, 아주 작은 메서드에 불과할 수도 있다.
목을 사용해도 같은 원칙이 적용된다. 동작 단위를 검증하는 데 필요한 목의 수는 관계가 없다.
비관리 의존성과의 통신에 관해서는 다음 두 가지 모두 확인하는 것이 중요하다.
이 요구 사항은 다시 비관리 의존성과 하위 호환성을 지켜야 하는데서 비롯된다. 호환성은 양방향이어야 한다. 즉, 애플리케이션은 외부 시스템이 예상하는 메시지를 생략해서는 안 되며 예상치 못한 메시지도 생성해서는 안 된다.
messageBusMock.Verfiy(x => x.SendEmailChangeMessage(user.UserId, "new@gmail.com"));
// 메시지를 전송하는지 확인하는 것만으로 충분하지 않다.
// 아래와 같이 메시지가 정확히 한 번만 전송되는지 확인해야 한다.
messageBusMock.Verfiy(x => x.SendEmailChangeMessage(user.UserId, "new@gmail.com"), Times.Once); // <- 해당 메서드를 한 번만 호출하는지 확인
서드파티 라이브러리 위에 항사 어댑터를 작성하고 기본 타입 대신 해당 어댑터를 목으로 처리해야 한다. 관련된 몇 가지 주장을 소개하면 다음과 같다.
실제로 어댑터는 코드와 외부 환경 사이의 손상 방지 계층으로 작동한다. 어댑터를 통해
또한 추상 계층을 두면 이러한 파급 효과를 하나의 클래스(어댑터 등)으로 제한할 수 있다.
보유 타입을 목으로 처리하라.
라는 지침은 프로세스 내부 의존성에 적용되지 않는다. 앞서 설명한 것처럼 목은 비관리 의존성에만 해당한다. 따라서 인메모리 의존성이나 관리 의존성을 추상화할 필요가 없다.