728x90
객체지향 프로그램에서는 여러가지 객체들이 서로 협력하면서 메시지를 주고 받는다.
그렇기 때문에 클래스의 독립적인 메서드를 테스트하기 위해서는 협력하는 상대를 가짜객체로 만들어서 테스트해야한다.
java에서 가짜객체를 만드는 방법은 mockito라는 방법이 있다. 이번에는 mockito에 대해 알아보자!
Mock이란?
- 한국어로 번역하면 "모조품"이라고 한다
- 객체지향 프로그램에서는 테스트를 수행할 모듈과 연결되는 외부의 다른 서비스나 모듈들을 흉내내는 가짜 모듈을 작성하여 테스트의 효용성을 높이는 데 사용하는 객체이다
- 독립적인 기능을 테스트하기 위해서 다른 연관된 객체들을 가짜로 만든다고 생각하면 된다
Mock 사용 예시
- 테스트를 할 때마다 데이터베이스를 띄우면 테스트하는 데 시간이 많이 걸릴 수 있다. 이 때 데이터베이스를 mock객체로 만들어 데이터베이스를 흉내내는 동작을 할 수 있다.
Mock을 사용하는 장점
- 테스트 시간이 줄어든다
- 불필요한 리소스 소비를 막는다
- 객체의 동작을 조정하여 발생할 수 있는 에러를 미리 처리할 수 있다
- 다른 객체의 오류가 내 테스트코드에도 영향을 미치는 걸 방지할 수 있음
Mockito
- mock을 쉽게 만들고 mock의 행동을 정하는 stubbing, 정상적으로 작동하는 지에 대한 verify 등 다양한 기능을 제공해주는 프레임워크이다.
Mock 사용법 - 공식홈페이지
공식홈페이지에 나오는 사용법은 다음과 같다
import static org.mockito.Mockito.*;
// mock creation
List mockedList = mock(List.class);
// or even simpler with Mockito 4.10.0+
// List mockedList = mock();
// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();
// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();
- 이런식으로 상호작용에 대해 검증할 수 있다고 한다
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// or even simpler with Mockito 4.10.0+
// LinkedList mockedList = mock();
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
- 이런 방식으로 stub 메서드 호출을 검증할 수 있다고 한다
- mockList.get(0)을 호출하면 "first"가 나오도록 가짜객체의 행동을 조절한다.
- mockList.get(0) 에는 미리 동작이 설정되어 있는데, 이를 stubbed되었다고 부르는 것 같다
class BaseballGameTest {
@DisplayName("정상적인 게임동작 테스트")
@Test
void goodGame() {
Comparator comparator = Mockito.mock(Comparator.class);
Map<String, Integer> map = new HashMap<>();
map.put("strike", 0);
map.put("ball", 0);
Mockito.when(comparator.getResult("123", "456")).thenReturn(map);
Map<String, Integer> result = comparator.getResult("123", "456");
assertEquals(result.get("strike"), 0);
}
}
- 이렇게 mock객체를 만들어 동작을 설정할 수 있다.
Mock 사용법2
아래 사이트에서 제공하는 사용법을 정리해봤다
https://www.baeldung.com/mockito-mock-methods
1. 기본 사용법
public static <T> T mock(Class<T> classToMock)
- mock 객체를 만드는 가장 간단한 방법은 class의 타입을 넣는 방법이다.
MyList listMock = mock(MyList.class);
when(listMock.add(anyString())).thenReturn(false);
boolean added = listMock.add(randomAlphabetic(6));
verify(listMock).add(anyString());
assertThat(added).isFalse();
- 다음과 같이 MyList라는 객체를 mock으로 만든다.
- add로 아무값이나 넣었을 때 false를 return하도록 동작을 설정한다(when().thenReturn)
- verity는 mock 객체에서 add 메서드가 호출되었음을 확인하는 부분이다. 위에서 add를 호출했기 때문에 에러가 나지 않는다.
- assertThat은 added가 false인지 테스트한다. 위에 listMock.add에 아무값이나 넣었을 때 false를 return하도록 동작을 설정하였으니, false가 return 될 것이다 -> 테스트 통과
- 만약 false가 아니면 AssertionError 예외가 발생한다
이름을 가지는 Mock
이름을 가지는 Mock을 사용할 수도 있다
이름은 작업 코드와는 아무 상관 없지만, 검증 오류를 추적할 때나 디버깅할 때 유용할 수 있다
발생한 예외 메시지에 이름이 포함되어있음을 확인하기 위해 AssertThatThrownBy를 사용한다고 한다.
public static <T> T mock(Class<T> classToMock, String name)
MyList listMock = mock(MyList.class, "myMock");
- 다음과 같이 mock객체에 myMock이라는 이름을 부여하였다
- Mock은 MyList.class에 대한 가짜객체이다
when(listMock.add(anyString())).thenReturn(false);
listMock.add(randomAlphabetic(6));
- listMock에 아무값이나 add를 하면 무조건 false가 나오게 설정하였다
assertThatThrownBy(() -> verify(listMock, times(2)).add(anyString()))
.isInstanceOf(TooFewActualInvocations.class);
assertThatThrownBy(() -> verify(listMock, times(2)).add(anyString()))
.isInstanceOf(TooFewActualInvocations.class)
.hasMessageContaining("myMock.add");
- AssertThatThrownBy 내에서 listMock에 add가 2번 호출되었는지 확인하고, 발생한 예외의 인스턴스가 TooFewActualInvocations.class인지 확인한다
- hasMessageContaining을 뒤에 붙이면 mock객체에 대한 정보를 포함해야 한다는 예외 메시지를 확인할 수 있다
- 이렇게 하면 다음과 같은 exception 메시지가 나온다
org.mockito.exceptions.verification.TooLittleActualInvocations:
myMock.add(<any>);
Wanted 2 times:
at com.baeldung.mockito.MockitoMockTest
.whenUsingMockWithName_thenCorrect(MockitoMockTest.java:...)
but was 1 time:
at com.baeldung.mockito.MockitoMockTest
.whenUsingMockWithName_thenCorrect(MockitoMockTest.java:...)
- myMock이라는 이름이 포함되어있는 걸 볼 수 있다
- 이를 통해 실패했을 때 실패지점을 빠르게 찾을 수 있다.
2. 응답을 포함하는 Mock생성
public static <T> T mock(Class<T> classToMock, Answer defaultAnswer)
- when, thenReturn을 생성할 때 미리 적용할 수도 있다
- 이렇게 하기 위해 Answer를 상속받는 커스텀 클래스를 만들고, 생성자에 넣어주면 된다
class CustomAnswer implements Answer<Boolean> {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
return false;
}
}
- 이렇게 Answer 인터페이스를 상속받은 커스텀 클래스를 정의하고
MyList listMock = mock(MyList.class, new CustomAnswer());
- mock을 생성할 때 넣어주면 적용이 된다
- (메서드의 기대값을 설정하지 않으면 기본 답변이 적용됨)
boolean added = listMock.add(randomAlphabetic(6));
verify(listMock).add(anyString());
assertThat(added).isFalse();
- when thenReturn을 적용하지 않았는데 테스트가 통과된다
3. MockSettings사용
- MockSettings 라는 걸 사용할 수도 있다
- 비표준 mock을 제공하기 위해 오버로딩된 메서드를 사용한다(MockSettings 인터페이스의 다양한 메서드)
- 현재 mock에서 메서드 호출을 위한 리스너 등록(invocationListeners)
- 직렬화를 구성(serializable)
- 감시할 인스턴스를 지정(spiedInstance)
- mock을 인스턴스화할 때 생성자를 사용을 시도하도록 Mockito 구성(useConstructor)
MockSettings customSettings = withSettings().defaultAnswer(new CustomAnswer());
MyList listMock = mock(MyList.class, customSettings);
- MockSettings 객체는 팩토리 메소드를 통해 인스턴스를 생성한다
- Answer를 상속받아 만든 CustomAnswer를 생성할 때 넣어준다
- mock을 생성할 때 생성자에 생성한 customSettings를 넣어준다
boolean added = listMock.add(randomAlphabetic(6));
verify(listMock).add(anyString());
assertThat(added).isFalse();
- CustomAnswer가 적용되었기 때문에 테스트는 잘 통과한다
간단한 사용법을 정리해봤고, 다음에는 다양한 상황에 적용해보면서 알아가려고 한다.
참고자료
https://ko.wikipedia.org/wiki/%EB%AA%A8%EC%9D%98_%EA%B0%9D%EC%B2%B4
https://www.baeldung.com/mockito-mock-methods
다음 글
728x90
'프로그래밍 언어 > Java' 카테고리의 다른 글
[JAVA] 문자열 양쪽 공백을 제거하는 trim과 strip 비교 (0) | 2023.10.29 |
---|---|
[JAVA] 단위테스트 공부2: 테코톡 보고 정리 (0) | 2023.10.29 |
[JAVA] public private 메소드 정렬 순서 결정하는법 (0) | 2023.10.28 |
[JAVA] StringBuilder 비우기(초기화하기) (1) | 2023.10.28 |
[JAVA] split vs StringTokenizer (1) | 2023.10.28 |