Mock: 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체.
Mockito: Mock을 지원하는 프레임워크로, 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공한다.
martinfowler.com/bliki/UnitTest.html
단위 테스트를 생각할 때, 클래스나 오브젝트, 또는 행동과 연관된 로직이라고 다양하게 생각할 수 있다.
나중에 테스트를 작성하게 되면 단위테스의 단위를 어떻게 정의할 것인가, Mock은 어느 범위에서 적용할 것인가 등을 함께 정하는 것이 좋다고 함
Mockito의 경우 스프링 부트를 사용하게 되면 spring-boot-starter-test의 의존성을 따라 자동으로 들어오게 된다.
만약 이 것을 사용하지 않은 경우엔 아래 2가지 의존성 추가가 필요하다.
- mockito-junit-jupiter: junit test에서 mockito를 연동해서 사용할 수 있는 추가적인 확장 구현체를 제공하는 라이브러리
- mockito-core: mockito가 제공하는 기본적인 기능들이 들어있는 라이브러리
Mock은 그럼 언제 사용하냐면?
외부 api등을 호출할 때, 어떻게 답이 오는지 가정 등을 하고 mock 객체로 만들어 이 것이 어떻게 동작하는지 테스트할 경우 등 사용한다.
또, 내가 만들고 있는 코드가 의존하는 클래스의 구현체는 없지만 인터페이스는 있고, 그 인터페이스 기반으로 코드를 작성할 때 그 것을 확인하기 위해 사용되기도 한다.
아래와 같은 두 가지 방법으로 Mock 객체를 만들 수 있다.
- Mockito.mock: 생성자로 주입하는 방법
- @Mock + @ExtendWith(MockitoExtension.class) - 함수 밖에 테스트 클래스의 필드로 주입할 수도 있고, 파라미터 전달도 가능하다
Mock 객체를 만든 후에는 Mock 객체의 행동을 조작해야한다. 이를 Stubbing이라고 한다.
모든 Mock 객체의 행동은 기본적으로 아래와 같이 행동한다.
- 리턴 타입이 있다면 Null 리턴한다. (Optional 타입은 Optional.empty 리턴)
- Primitive 타입은 프리미티브 기본 값을 따름
- 콜렉션은 비어있는 콜렉션으로 만들어준다
- Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다
Mock 객체를 조작하기 위해서는
- Mockito.when(/*조건*/).thenReturn(/*객체*/): 조건에 해당한다면 무조건 어떠한 객체를 리턴하라
- Mockito.when(/*조건*/).thenReturn(/*객체*/).thenThrow~~.thenReturn~~: 메소드가 여러 번 호출될 때 stubbing을 같은 매개변수로 호출 되더라도 호출되는 순서에 따라 다르게 mocking. 즉, 동일한 조건에 대해 다양한 객체/예외를 리턴하도록 설정해 둘 수 있다.
- doThrow(/*Exception*/).when(/*조건*/): 조건에 해당한다면 어떠한 예외를 던져라
마지막으로 Mockito로 만든 Mock 객체가 어떻게 사용됐는지(객체에 어떤 일이 일어났는지) 확인하는 방법이다.
- verify(memberService, times(1)).notify(study): 예를 들어 mock객체 memberService의 함수 notify가 1번만 호출되어야 한다
- inOrder(memberService): 함수 호출에 대해 순서를 부여하고 싶은 경우
다른 여러가지 조건은 아래 공식문서를 확인하도록 하자
javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#verification_timeout
Mockito에서는 별개로 BDD 스타일의 테스트를 할 수 있는 방법을 BddMockito 클래스를 통해 제공한다.
BDD (Behaviour-Driven Development): 애플리케이션이 어떻게 “행동”해야 하는지에 대한 공통된 이해를 구성하는 방법으로, TDD에서 창안했다.
BDD는 시나리오를 기반으로 테스트 케이스를 작성하며 함수 단위 테스트를 권장하지 않는다. 이 시나리오는 개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 레벨을 권장한다. 하나의 시나리오는 Given, When, Then 구조를 가지는 것을 기본 패턴으로 권장하며 각 절의 의미는 다음과 같다.
Feature : 테스트에 대상의 기능/책임을 명시한다
Scenario : 테스트 목적에 대한 상황을 설명한다
Given : 시나리오 진행에 필요한 값을 설정한다 - 어떤 상황이 주어졌을 때
When : 시나리오를 진행하는데 필요한 조건을 명시한다 - 무엇인가를 하면
Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시한다 - ~~할 것이다
위의 내용을 개발 측면에서 더 간략하게 정리하면 테스트 대상의 상태 변화를 테스트하는 것이다.
테스트 대상은 A 상태에서 출발하며(Given) 어떤 상태 변화를 가했을 때(When) 기대하는 상태로 완료되어야 한다. (Then)
또는 Side Effect가 전혀 없는 테스트 대상이라면 테스트 대상의 환경을 A 상태에 두고(Given) 어떤 행동을 요구했을 때(When) 기대하는 결과를 돌려받아야 한다. (Then)
'Java (+ Spring)' 카테고리의 다른 글
Application의 성능 테스트하기: JMeter (0) | 2021.02.11 |
---|---|
도커로 테스트 하기: TestContainers (0) | 2021.02.10 |
Java Unit Test: Junit5 (0) | 2021.02.10 |
Spring Boot: Spring Security & 카카오 로그인 API(...ing) (0) | 2020.10.06 |
Spring boot: JPA 설정 방법 및 주의할 점 (1) | 2020.07.31 |