BDD라는 용어는 2006년 Dan North에 의해 처음 만들어졌습니다.
BDD는 애플리케이션의 동작(behavior)에 초점을 맞춘 자연스럽고 사람이 읽을 수 있는 언어로 테스트를 작성하도록 권장합니다.
이는 세 가지 섹션(Arrange, Act, Assert)에 따라 명확하게 구조화된 테스트 작성 방법을 정의합니다.
Mockito 라이브러리는 BDD 친화적인 API를 도입하는 BDDMockito 클래스와 함께 제공됩니다. 이 API를 사용하면 given()을 사용하여 테스트를 어레인지하고 then()을 사용하여 어설션을 만드는 BDD 친화적인 접근 방식을 취할 수 있습니다.
이 기사에서는 BDD 기반 Mockito 테스트를 설정하는 방법을 설명하겠습니다. 또한 Mockito와 BDDMockito API의 차이점에 대해 이야기하고 결국 BDDMockito API에 중점을 둘 것입니다.
Mockito의 BDD 버전은 mockito-core 라이브러리의 일부입니다. 시작하려면 아티팩트만 포함하면 됩니다.
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.21.0</version>
</dependency>
다음 정적 import를 포함하면 테스트를 더 쉽게 읽을 수 있습니다.
import static org.mockito.BDDMockito.*;
BDDMockito는 Mockito를 확장하므로 기존 Mockito API에서 제공하는 기능을 놓치지 않습니다.
Mockito의 전통적인 mocking은 어레인지 단계에서 when(obj).then*()을 사용하여 수행됩니다.
나중에 Assert 단계에서 verify()를 사용하여 모의 객체와의 상호 작용을 확인할 수 있습니다.
BDDMockito는 다양한 Mockito 메소드에 대한 BDD 별칭(aliases)을 제공하므로 given(when 대신)을 사용하여 어레인지 단계를 작성할 수 있으며 마찬가지로 verify 대신 then을 사용하여 Assert 단계를 작성할 수 있습니다.
전통적인 Mockito를 사용하는 테스트 본문의 예를 살펴보겠습니다.
when(phoneBookRepository.contains(momContactName))
.thenReturn(false);
phoneBookService.register(momContactName, momPhoneNumber);
verify(phoneBookRepository)
.insert(momContactName, momPhoneNumber);
BDDMockito와 어떻게 비교되는지 살펴보겠습니다.
given(phoneBookRepository.contains(momContactName))
.willReturn(false);
phoneBookService.register(momContactName, momPhoneNumber);
then(phoneBookRepository)
.should()
.insert(momContactName, momPhoneNumber);
PhoneBookRepository를 모의해야 하는 PhoneBookService를 테스트해 보겠습니다.
public class PhoneBookService {
private PhoneBookRepository phoneBookRepository;
public void register(String name, String phone) {
if(!name.isEmpty() && !phone.isEmpty()
&& !phoneBookRepository.contains(name)) {
phoneBookRepository.insert(name, phone);
}
}
public String search(String name) {
if(!name.isEmpty() && phoneBookRepository.contains(name)) {
return phoneBookRepository.getPhoneNumberByContactName(name);
}
return null;
}
}
Mockito로서의 BDDMockito를 사용하면 고정되거나 동적일 수 있는 값을 반환할 수 있습니다. 또한 예외를 던질 수도 있습니다:
BDDMockito를 사용하면 모의 개체 대상 메서드가 호출될 때마다 고정된 결과를 반환하도록 Mockito를 쉽게 구성할 수 있습니다.
given(phoneBookRepository.contains(momContactName))
.willReturn(false);
phoneBookService.register(xContactName, "");
then(phoneBookRepository)
.should(never())
.insert(momContactName, momPhoneNumber);
BDDMockito를 사용하면 값을 반환하는 보다 정교한 방법을 제공할 수 있습니다. 입력을 기반으로 동적 결과를 반환할 수 있습니다.
given(phoneBookRepository.contains(momContactName))
.willReturn(true);
given(phoneBookRepository.getPhoneNumberByContactName(momContactName))
.will((InvocationOnMock invocation) ->
invocation.getArgument(0).equals(momContactName)
? momPhoneNumber
: null);
phoneBookService.search(momContactName);
then(phoneBookRepository)
.should()
.getPhoneNumberByContactName(momContactName);
Mockito에게 예외를 발생시키라고 지시하는 것은 매우 간단합니다:
given(phoneBookRepository.contains(xContactName))
.willReturn(false);
willThrow(new RuntimeException())
.given(phoneBookRepository)
.insert(any(String.class), eq(tooLongPhoneNumber));
try {
phoneBookService.register(xContactName, tooLongPhoneNumber);
fail("Should throw exception");
} catch (RuntimeException ex) { }
then(phoneBookRepository)
.should(never())
.insert(momContactName, tooLongPhoneNumber);
주어진 위치와 will*의 위치를 어떻게 교환했는지 주목하세요. 반환 값이 없는 메서드를 조롱하는 경우에는 필수입니다.
또한 (any, eq)와 같은 인수 일치자를 사용하여 고정 값에 의존하는 대신 기준에 따라 보다 일반적인 모의 방법을 제공했다는 점에 유의하세요.