최근에 백엔드 개발자들과 테스트에 대한 이야기를 나누다보면 Mocking에 대해 어떻게 생각하는지, 또는 Mock을 써도 되냐 안되냐 같은 질문을 종종 접합니다. 사실 Mock이 무엇을 의미하는지 몰라서 사전을 찾아보니 ‘가짜의’, ‘모의의’ 이런 뜻이네요. 그러고 보니 저도 비슷한 걸 단위테스트를 위해 만들긴 합니다.
저는 하드웨어 API 서버와 연동하는 시스템 컴포넌트를 개발하는 일을 주로 하는데 개발 대상이 되는 하드웨어 하나가 생기면 항상 모의 소프트웨어를 개발해서 단위테스트에 활용합니다. 하드웨어는 실제로 테스트를 위해 마음껏 물려보기 힘든 경우가 많고 단위 테스트 결과의 일관성을 위해 하드웨어 상태를 조작하는데 어려움도 있습니다. 그래서 하드웨어 API 서버와의 통신 인터페이스를 모의해 주는 소프트웨어를 개발해서 단위 테스트에 활용합니다.
지난주 토비의 스프링 '2장 테스트' 읽기모임 중에 이동욱님의 글이 질문으로 올라왔습니다. 그리고 MockBean을 사용하는 것에 대해 어떻게 생각하는지에 대한 질문이 공유되었습니다. 인프콘에서도 테스트에 대한 발표를 한 후 네트워킹 시간에 누군가 Mocking에 대한 질문을 했었는데 사용해 본적이 없어서 답변을 해 주지 못했습니다. 이 참에 이 정도는 알고 있어야 겠다는 생각이 들어서 간단히 학습 테스트 코드를 작성해 봅니다.
우선 Mocking할 SampleBean이라는 클래스를 정의합니다. 스프링 컨텍스트에서 관리하는 빈이 되도록 Component 어노테이션을 붙여주고 간단히 “hello” 문자열을 반환하는 public 메서드를 정의합니다.
@Component
public class SampleBean {
public String hello() {
return "hello";
}
}
이제 JUnit 테스트 코드에서 SampleBean을 Mocking 하고 hello() 메서드를 호출하는 경우와 Mocking하지 않고 의존관계 주입을 받아 hello() 메서드를 호출하는 경우 어떤 차이점이 있는지 확인해 봅니다. 코드가 간단하니 결과도 간단합니다. 먼저 SampleBean을 Mocking 하는 경우에는 hello() 호출결과로 null 값이 반환됩니다. Mocking 하지 않는 경우에는 의도한 대로 “hello” 문자열이 반환됩니다.
// 1. Mocking 하는 예제
@SpringBootTest(classes = {SampleBean.class})
public class MockingBeanTest {
@MockBean
SampleBean mockBean;
@Test
@DisplayName("Mock Bean 테스트")
void mockBeanHello() {
Assertions.assertNull(mockBean.hello());
}
}
// 2. Mocking 하지 않는 예제
@SpringBootTest(classes = {SampleBean.class})
public class RealBeanTest {
@Autowired
SampleBean realBean;
@Test
@DisplayName("Real Bean 테스트")
void realBeanHello() {
Assertions.assertEquals("hello", realBean.hello());
}
}
위 예제 코드에서는 기본적인 Mock 빈을 사용했기 때문에 반환값이 null로 옮니다. 인터페이스 껍데기만 정말 구현된 것입니다. 그러나 Mockito 라이브러리를 사용하면 아래와 같이 간단하게 Mock 빈의 반환 값 역시 모의해 줄 수 있습니다.
@Test
@DisplayName("특정 입력을 모의한다")
void inputFromMock() {
Mockito.when(mockBean.hello()).thenReturn("Hi");
Assertions.assertEquals("Hi", mockBean.hello());
}
MockBean 어노테이션을 쓰면 뭔가 놀랄만한 일이 일어나는 줄 알았는데 그런건 아니었네요. 단지 실제 빈의 인터페이스 껍데기를 구현한 가짜 빈을 만들고 실제 빈을 대체해 주는 것이었습니다. 그래서 특정 객체의 단위 테스트를 작성할 때 의존 객체와의 인터랙션을 정상이라고 고정해 두고 내부 로직을 검증하기 위해 사용하는 목적인 것 같습니다. 시간을 내어 Unit Test 책과 Effective Unit Testing 책을 한 번 심도 있게 공부해 보고 싶은 마음이 듭니다.