Mockito??

권 해·2023년 1월 10일
0

Spring

목록 보기
6/9

TDD방법론을 적용하여 진행하기로 한 MyMiChef 프로젝트를 시작하자 마자 위기에 마주했다.
TDD를 적용한 다른 사람들의 예제를 살펴보면, 많은 사람들이 Mockito를 사용한다는 것이다.
@Mock, @InjectMocks, @Spy 등등 처음보는 어노테이션들도 많이 등장하여 당황했다.
Mockito에 대해서 여러 자료를 찾아보고 했지만, 너무 머리아프고 헷갈리는 부분이 많았기 때문에,
따로 포스팅하여 다시 정리한다.

Mockito란

Mockito를 알기 전에 Mock이 무엇인지 부터 알아야 한다.
Mock이란 사전에는 "가짜의"라는 뜻을 가지고 있다. 위키백과에 정의 된 Mock이란 다음과 같다.

모의 객체(Mock Object)란 주로 객체 지향 프로그래밍으로 개발한 프로그램을 테스트할 경우 테스트를 수행할 모듈과 연결되는 외부의 다른 서비스나 모듈들을 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 "흉내"내는 "가짜" 모듈을 작성하여 테스트의 효용성을 높이는 데 사용하는 객체이다. 사용자 인터페이스(UI)나 데이터베이스 테스트 등과 같이 자동화된 테스트를 수행하기 어려운 때 널리 사용된다

위 설명만으로는 감이 잘 잡히지 않을 수 있다.
쉽게 말해서 Mock 객체란 가짜 객체라는 뜻이다. 껍데기만 가지고, 있는 객체이다.
예를 들어 회원 조회 서비스를 만들기 위해 테스트를 하고 있다고 하자.
원래 회원 조회를 테스트 하기 위해서는, 회원이 DB상에 있는지 조회하고 정보를 가져와야 한다.
테스트를 실행할때마다 레포지토리의 findByID과 같은 메서드를 통해 DB에서 읽어온다면, 부하도 많이 걸리고 시간도 많이 걸릴 것이다.
하지만, 레포지토리 객체를 Mock 객체, 즉 가짜 객체로 만들어 준다면 DB에 접근을 하지 않고도 테스트를 할 수 있다.(어떻게 하는지는 아래의 Mockito 사용법에서 다시 다루도록 하겠다)
이렇게 mock 객체를 만들면 테스트 시간도 줄이면서 불필요한 리소스 소비를 막을 수 있다.

Mockito란 개발자가 동작을 직접 제어할 수 있는 가짜(Mock) 객체를 지원하는 테스트의 프레임워크이다. Spring으로 개발을 하면 일반적으로 여러 객체들 간의 의존성이 생긴다. 이러한 의존성은 단위 테스트 작성을 어렵게 하는데, 이를 해결하기 위해 가짜 객체를 주입시켜주는 Mockito 라이브러리를 활용할 수 있다.

내가 이해하기로 Mockito를 사용하는 이유중 가장 큰 것은, 의존성에 구애받지 않고 독립적인 테스트를 할 수 있다는 점이다.
단위 테스트를 할 때 가장 중요한 것은, 각 컴포넌트들이 다른 컴포넌트와는 완전히 독립적이어야 한다는 점이라고 생각한다.
예를 들어 회원조회 서비스를 테스트할 때 Mock 객체를 이용하는 상황에서,
회원 조회 서비스를 실행하기 위해서는 회원 레포지토리에 포함된 회원 정보를 찾는 기능을 사용해야 한다.
이렇다면 회원 조회 서비스는 회원 레포지토리의 기능에 의존하는 것이기 때문에 올바른 테스트가 아니다.
이럴때 회원 레토지토를 Mock 객체로 만들어 주면, 독립적인 테스트가 가능하다.
위와 같은 이유 뿐만 아니라, 테스트를 할 때마다 DB에 접근하지 않아도 된다는 이점이 있다.

Mockito 사용법

Mockito에서 Mock 객체의 의존성 주입을 위해서는 크게 3가지 어노테이션이 사용된다.

  • @Mock: Mock 객체를 만들어 반환해주는 어노테이션
  • @InjectMocks: @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션
  • @Spy: Stub하지 않은 메소드들은 원본 메소드 그대로 사용하는 어노테이션

위에서 stub이란 메서드를 호출하면 미리 정의된 답변을 주도록 하는 것을 말한다.
Mock으로 생성되어있는 객체는 그저 껍데기에 불과하기 때문에 stub을 통해, 메서드가 호출되었을 때 나와야 할 답에 대해 미리 정의해 주어야 하다.
하지만 Spy로 생성된 객체는 원본 메소드를 쓸 수 있으면서도, stub을 통해 미리 답변을 정해놓을 수도 있다.
@InjectMocks 어노테이션은 의존성 주입과 비슷하다. 이 어노테이션을 쓰면, @Mock이나 @Spy로 생성된 객체들을 주입시켜 준다.

이제 테스트를 하기 위해서는 Mock객체 또는 Spy객체(이건 잘 안쓰긴 한다)를 생성해고, stub하여야 한다.
실제로 내가 프로젝트중 작성한 테스트 코드를 통해 설명하도록 하겠다.

이 테스트는 loadUserByUsername() 메서드를 TDD방식으로 구현하기 위한 코드이다.
loadUserByUsername메소드는 userId를 입력받아 회원이 존재하는지 검사하고, 회원이 존재하면 회원정보가 담긴 객체를 반환하여 주는 메소드이다.

 @Test
    public void 유저정보_찾지못함(){
        //given
        doReturn(Optional.empty()).when(userRepository).findById(userId);
        //when
        final UsernameNotFoundException result=assertThrows(UsernameNotFoundException.class,()->
                                                                userService.loadUserByUsername(userId));
        //then
        assertThat(result.getMessage()).isEqualTo("계정이 존재하지 않습니다.");
    }

먼저 테스트는 기본적으로 given, when, then 과정을 통해 이루어진다.
given 에서는 stub 과정이 필요하다.
doReturn() 메소드를 통해 미리 정해집 답을 반환할 수 있도록 정의할 수 있다.
위 코드 중

doReturn(Optional.empty()).when(userRepository).findById(userId);

이 부분은 userRepository.findById(userId)가 호출되었을 때, Optional.empty()를 반환하라는 뜻이다. 미리 Optional.empty()로 답을 정해 놓은 것이다.
왜 미리 답을 정해두었냐 하면, 이 테스트에서는 회원을 찾지 못했을 때 정상적으로 이 서비스가 작동이 되는가를 판단하기 위한 목적이 있는데, 미리 답을 정해주지 않으면 레포지토리에서 findById를 직접 실행하고 db에 접근해야한다. 이 부분은 우리가 궁금하지 않기 때문에, 그냥 유저정보를 찾지 못했다고 가정하고, 이 서비스를 실행하는 것이다.

when 부분에서는 loadUserByUsername(userID)를 호출하였을때, throw 하는 exception을 result에 저장한다는 것이다.
이때, loadUserByUsername 메소드에는 회원 정보를 찾지 못했을 때, exception을 throw 하도록 구현해 두었다.

then에서는, 실제로 when 절에서 던져진 exception의 에러메시지와, 우리가 예상하는 에러메시지가 같다면 통과하도록 되어있다.
만약.

위와 같은 방법으로 Mockito가 테스트에 사용된다.
이렇게 정리해 보니 뭔가 간단해 보이는 것 같기도 하다.
처음엔 사용하는것이 너무 생소하고 어려웠지만, TDD에 익숙해지기 위해서는 견뎌야 한다.

0개의 댓글