Mockito 살펴보기

Rudy Lee (이재훈)·2021년 6월 24일
1
post-thumbnail

Mockito

배경 지식

일반적인 테스트와 모의 객체 테스트 비교

일반적인 테스트모의 객체 테스트
테스트 대상 객체실제 객체모의 객체
검증 방식상태(state) 검증행위(behavior) 검증
셋업 방식협력 객체 생성 및 최초 데이터 적재모의 객체 생성 및 모의 객체에 기대되는 행위 선언
관심사행위의 결과객체간 메시지 통신
  • 상태 검증(state verification)
    • 테스트 할 기능이 실행 된 후에, 테스트 대상 객체와 협력 객체의 상태가 기대되는 상태인지 검증한다.
  • 행위 검증(behavior verification)
    • 테스트 할 기능이 실행된 후에, 테스트 대상 객체와 협력 객체가 기대하는 순서대로 메시지를 주고 받았는지(호출이 일어났는지) 검증한다.

테스트 대역(Test Double)

테스트 대역은 테스팅을 목적으로 진짜 객체대신 사용되는 모든 종류의 위장 객체를 지칭한다.

  • 더미(Dummy)
    • 전달되기만 하고 실제 사용되지는 않는 객체
    • ex. 파라미터 리스트를 채우는 데 사용
  • 스텁(Stub)
    • 특정 요청에 대해 미리 준비된 응답을 하는 객체
    • 프로그램된 것 이외의 것에 대해서는 응답하지 않는다.
    • 호출에 대한 정보를 기록하는 스텁은 스파이(Spy)라고 부르기도 한다.
  • 가짜(Fake)
    • 동작하는 구현이 있지만 운영시에는 사용할 수 없는 간단한 형태의 구현체
    • ex. 인메모리 데이터베이스로 사용
  • 모의객체(Mock)
    • 기대하는 호출의 명세대로 미리 프로그램 된 객체
  • 모의객체적 테스팅의 한 분파: 행위 주도 개발(Behavior Driven Development; BDD)

BDD Style Specification

  • Title
  • Narrative
    • As a ~
    • I want ~
    • So that ~
  • Acceptance Criteria
    • Given
    • When
    • Then

출처: 모의객체는 스텁이 아니다

개요

Mockito란?

Mockito의 장점

  • 깨끗하고 간단한 API로 테스트를 작성할 수 있다.

  • 테스트 코드의 가독성이 높다.

  • 명확한 검증 오류가 발생한다.

환경 설정

plugins {
	id 'java'
}

repositories {
	mavenCentral()
}

dependencies {
	// JUnit 5
	testImplementation(platform('org.junit:junit-bom:5.7.2'))
	testImplementation('org.junit.jupiter:junit-jupiter')
	
	// Mockito
	testImplementation('org.mockito:mockito-core:3.11.2')
	testImplementation('org.mockito:mockito-junit-jupiter:3.11.2')
}

test {
	useJUnitPlatform()
}

Mockito 살펴보기

  • mock()/@Mock: 모의 객체를 생성한다.
    • Answer/MockSettings를 통해 동작 방식을 명시할 수 있다.
    • when()/given()을 이용해 모의 객체가 어떻게 동작해야 하는지 명시할 수 있다.
    • 원한다면 Answer를 확장할 수도 있다.
  • spy()/@Spy: 스파이를 생성한다.
    • 실제 메서드가 호출되지만, 스텁과 검증이 가능하다.
  • @InjectMocks: @Spy 또는 @Mock으로 선언된 필드에 객체를 자동으로 주입한다.
  • verify(): 메서드가 주어진 인자로 호출되었는지 검증한다.
    • 유연한 인자 매칭을 사용할 수 있다. (ex. any())
    • @Captor를 이용하여 어떤 인자로 호출되었는지 확인할 수 있다.
  • BDDMockito는 행위 주도 개발(BDD) 구문을 지원한다.
    • Mockito.whenBDDMockito.given
    • Mockito.verifyBDDMockito.then

Mockito 맛보기

상호작용 검증

// mock creation
List mockedList = mock(List.class);

// 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);

// 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));

Mockito 테스트 작성 방식

  1. 모의 객체 생성(Create): 원하는 클래스 혹은 인터페이스로 모의 객체를 생성한다.
  2. 스텁(Stub): 생성한 모의 객체의 동작을 지정한다.
  3. 실행(Use/Exercise): 모의 객체를 사용하여 테스트할 기능을 실행한다.
  4. 검증(Verify): 메서드가 기대한대로 호출되었는지 검증한다.

기억할 것

  • Keep the testing code compact and readable.
    테스트 코드를 간결하고 가독성 좋게 유지하라.

  • Do not mock types you don’t own.
    당신이 소유하지 않은 타입은 모의 객체로 만들지 말라.

  • Don’t mock value objects.
    값 객체는 모의 객체로 만들지 말라.

  • Don’t mock everything.
    모든 것을 모의 객체로 만들지 말라.

Mockito 기초

모의 객체 생성

  • mock() 또는 spy()를 이용해서 생성한다.
  • @ExtendWith(MockitoExtension.class)로 익스텐션을 등록하고 @Mock 또는 @Spy 애노테이션으로 모의 객체를 생성할 대상을 지정한다.
  • 클래스, 인터페이스, 추상 클래스에 대해 모의 객체를 생성할 수 있다.

스텁 설정

  • 모의 객체를 생성한 뒤 모의 객체에 스텁을 구성할 수 있다.
  • 모의 객체의 메서드 호출을 전달하고 특정 값을 리턴하거나, 예외를 던지도록 스텁한다.
    • Mockito.when(mock.action())
      • .thenReturn(true)
      • .thenThrow(Exception.class)
    • BDDMockito.given(mock.action())
      • .willReturn(true)
      • .willThrow(Exception.class)
  • 만일 일치하는 스텁 설정이 없을 경우 리턴 타입의 기본 값을 리턴한다.
    • 원시 타입 → 원시 타입의 기본값
    • 콜렉션 타입 → 빈 콜렉션
    • 객체 타입 → null
    • Optional 타입 → Optional.empty()
    • void 타입 → 아무것도 하지 않는다.

행위 검증

  • 모의 객체의 역할 중 하나는 실제로 모의 객체가 불렸는지 검증하는 것이다.

  • 예시) 모의 객체 mockaction() 메서드가 호출되었음을 검증하기

    • Mockito.verify(mock).action()
    • BDDMockito.then(mock).should().action()
      • should()로 모의 객체의 메서드가 불려야 한다고 설정하고, should() 메서드 다음에 실제로 불려야 할 메서드를 지정한다.
  • 정확한 값이 아니라 메서드가 불렸는지 여부가 중요하다면 인자 매칭을 이용한다.

  • 메서드 호출 횟수를 검증하려면 should() 메서드에 다음과 같은 메서드를 인자로 전달한다.
    only(): 한 번만 호출
    times(int): 지정한 횟수만큼 호출
    never(): 호출하지 않음
    atLeast(int): 적어도 지정한 횟수만큼 호출
    atLeastOnce(): atLeast(1)과 동일
    atMost(int): 최대 지정한 횟수만큼 호출

인자 매칭

  • ArgumentMatchers 클래스를 이용하면 정확하게 일치하는 값 대신 임의의 값에 일치하도록 설정할 수 있다.
    • any*(): 해당 타입에 대한 임의 값 일치
      • ex. any(), anyInt(), anyShort(), anyLong(), anyByte(), anyChar(), anyDouble(), anyFloat(), anyBoolean(), anyString(), anyList(), anySet(), anyMap(), anyCollection()
    • matches(String|Pattern): 정규 표현식을 이용한 String 값 일치 여부
    • eq(): 특정 값에 대한 일치

인자 캡쳐

  • 모의 객체를 호출할 때 사용한 인자를 검증해야 할 경우 사용한다.
  • ArgumentCaptor를 사용하면 메서드 호출 여부를 검증하는 과정에서 실제 호출할 때 전달한 인자를 보관할 수 있다.
    • 모의 객체 호출 여부를 검증하는 코드에서 인자로 capture() 메서드를 전달하고
    • getValue() 메서드로 실제 인자 값을 가져와서 검증에 사용한다.

0개의 댓글