Kent Beck의 XP(Extreme Programming) Explained 저서에는 이런 문구가 있습니다.
개발의 기본 흐름은 일단 실패하는 테스트를 작성하고, 그 다음으로 그 테스트를 통과하도록 만드는 것이다. … 해결하고 싶은 스토리들을 목록으로 만들고, 그 스토리들을 표현하는 테스트들을 작성하고, 그런 다음 그 테스트들 통과하도록 만든다. 여러분이 작성해야 할 필요가 있다고 생각하는 테스트들을 목록으로 만들고, 테스트를 하나 작성하고, 그 테스트를 통과하도록 만들고, 다른 테스트를 작성하고, 두 테스트 모두 통과하도록 만들고 하면서 목록이 비워질 때까지 일한다.
이번에 VmWare Tanzu Labs분들에게 TDD로 진행하는 법을 배우면서, 테스트 주도 개발(TDD, Test Driven Development)는 실천을 통해서 얻을 수 있는 느낌을 많이 받았다. TDD를 어느 상황에 어떻게 한다 라고 정의 내리긴 힘들고 각 상황별, 자기가 테스트하고 싶은 대상에 따라, TDD를 진행하는 방식이 달라질수 있다는 느낌을 많이 받았다.
그렇지만, 개발자는 TDD를 실천하기 위한 몇가지 도구들이 필요하고, 그 중 Test Double의 개념 설명과 그 것을 구현하는 도구인 Mockito에서 지원하는 Mock, Spy에 대해 간략하게 소개하고자 한다.
1. Test Double
테스트 더블(Test Double)이라는 단어는 영화 산업에서 위험한 장면을 촬영할 때 배우를 대체할 대역인 스턴트 더블(stunt double)에서 유래했습니다. 테스트를 진행할 때 실제 클래스를 사용하는 것이 아니라 이와 동일한 형태를 가진 테스트 더블을 사용합니다.
1.1. Why Using Test Double
단위 테스트(unit test)는 시스템이 커질수록 쉽지 않아지기 마련입니다. 테스트하고 싶은 메소드 내부에 다른 컴포넌트(component)에 의존한 기능들로 인해 결합도(coupling)가 높을수도 있고, 제어하기 어려운 네트워크나 데이터베이스를 사용하는 기능들이 존재할 수 있습니다. 이런 여러 가지 제약 사항들 때문에 어려운 테스트를 빠르고 쉽게 진행하기 위해 테스트 더블을 사용합니다.
시스템 컴포넌트 단위 테스트
https://www.crocus.co.kr/1555
1.2. When Using Test Double
테스트 더블은 다음과 같은 시기에 사용합니다.
예측 불가능한 요소를 통제하여 테스트하기를 원하는 경우
느린 테스트를 보다 빠르게 진행하기를 원하는 경우
통합 환경 구축의 어려움이 발생하는 경우
실제 클래스를 사용하기 어렵고 불편한 경우
2. Test Double 소개
테스트를 도와주는 개념적인 요소들이다.
Test Double에는 5가지 Type이 있다.
TestDouble의 5가지 Type
2.1. Test Double Type별 소개
여기서 sut(System Under Test)가 의미하는 것은 테스트하고자 하는 대상을 말한다.
여기서 예제 코드들은 Mockito의 Mock을 사용하여 대부분 작성하였다.
Mock
Mock
sut의 메소드가 실행될때, 일어나는 sut의 어떤 행위를 검증하고자 할때, 사용한다.
예를들면, sut는 BarController이고, BarController 메소드를 실행하면, BarAdaptor.getLineCount(request)이란 행위를 한다고 하자
이때, 아래와 같이 검증할수 있고, 이것을 Mock이라고 한다.
verify(BarAdaptor).getLineCount(request);
Spy
Spy
직접 customizing한 mock이라고 할 수 있다.
테스트에 사용되는 객체, 메소드의 사용 여부 및 정상 호출여부를 주로 알려주기 위해 사용되며, 테스트에 사용되는 객체를 implements 또는 extends 받아서, @Override 해서 직접 메소드를 customizing 해서 테스트할 수 있다.
customizing
public class BarClientSpy implements BarClient {
private final List<Integer> callSequences = new ArrayList<>();
public List<Integer> getCallSequences() {
return this.callSequences;
}
@Override
public BarResDTO bar(BarReqDTO request) {
callSequences.add(1);
return stubResponse();
}
private BarResDTO stubResponse() {
return BarResDTO.builder
.cd("sampleCd")
.msg("sampleMsg")
.build();
}
}
Stub
Stub
sut의 메소드가 실행될 때, 일어나는 어떤 행위를 정의(준비해둔 결과를 반환)해줄 때 사용한다.
어떤걸 리턴해줘라(행위)
.thenAnswer(추가 제어; return 반환값)
어떤 행위(when)을 했을때, 필연적으로 제어하는 로직이 필요한 경우, thenAnswer를 이용할 수 있다.
1. 이 책은 자바 언어와 그 기반 라이브러리, 즉 java.lang, java.util, java.io, java.util.concurrent, java.util.function 같은 하위 패키지를 효과적으로 사용하게끔 구성했음(때때로 다른 패키지도 다룸)
2. 많은 디자인 패턴과 관용구를 설명해줌
3. 이 책의 규칙 대부분은 아주 핵심적인 기본 원칙 몇 개에서 파생됨 바로 명료성과 단순성임. - 컴포넌트는 사용자를 놀라게하는 동작을 해서는 절대는 안됨. - 코드는 복사되는게 아니라 재사용되어야함. - 컴포넌트 사이의 의존성은 최소로 유지해야함.
4. 기술 용어는 대부분 자바 8용 언어 명세를 따르며, 주요사항은 아래와 같음 - 자바가 지원하는 타입은 인터페이스, 클래스, 배열, 기본타입 까지 총 4가지임. - 애너테이션(annotation)은 인터페이스의 일종 - 열거 타입(enum)은 클래스의 일종 - 인터페이스, 클래스, 배열은 참조타입(reference type)이라고 함 - 즉, 인터페이스, 클래스, 배열은 객체(object)이고 기본타입은 그렇지 않음 - 클래스의 멤버로는 필드, 매서드, 멤버 클래스, 멤버 인터페이스가 있음 - 메서드 시그니처는 메서드의 이름과 입력 매개변수(parameter)의 타입들로 이뤄짐(반환값의 타입은 시그니처에 포함되지 않음 )
* 참고 메소드 시그니처(method signature) 메소드 오버로딩의 핵심은 바로 메소드 시그니처(method signature)에 있습니다. 메소드 시그니처란 메소드의 선언부에 명시되는 매개변수의 리스트를 가리킵니다. 만약 두 메소드가 매개변수의 개수와 타입, 그 순서까지 모두 같다면, 이 두 메소드의 시그니처는 같다고 할 수 있습니다.