Mockito는 Mock 객체를 쉽게 만들고, 관리하고, 검증할 수 있는 방법을 제공하는 프레임워크이다. Mock 객체는 실 객체와 비슷하게 동작하지만, 프로그래머가 직접 행동을 관리하는 객체이다.
Mapper, Reducer Test
Mockito를 이용하여 작성한 WordCount 작업의 Mapper, Reducer 테스트 코드는 다음과 같다.
@Test
// Mockito 를 통해 테스트를 진행한다.
// 객체의 멤버, 메소드의 변경, 호출 등을 관리 및 검증할 수 있는 Mock 객체를 이용한다.
public void wordCountMapTestWithMockito1() throws IOException, InterruptedException {
/* GIVEN */
// 1. 테스트 할 Mapper 의 객체를 생성한다.
WordCount.TokenizeMapper mapper = new WordCount.TokenizeMapper();
// 2. 테스트 할 map 메소드의 인자들을 mock 객체로 생성한다.
// key 와 value 는 검증할 필요가 없기 때문에 실제 객체로 생성한다.
LongWritable key = new LongWritable(0L);
Text value = new Text("dog dog cat owl dog cow cat owl dog");
// map 메소드 내 mapper.word.set, context.write 메소드의 정상 호출 여부를 검증하기 위해 mock 객체로 생성한다.
mapper.word = mock(Text.class);
Mapper.Context context = mock(Mapper.Context.class);
/* WHEN */
// 3. 인자를 전달하여 테스트를 수행한다.
mapper.map(key, value, context);
/* THEN */
// 4. 테스트 결과를 검증한다.
// 메소드 호출 순서를 검증하기 위해선 InOrder 객체를 생성해야 한다.
// 정상적인 호출 순서는 word.set - context.write - word.set - context.write - ... 이다.
// 이를 검증하기 위해 mapper 의 word 와 context 를 인자로 넘겨 생성한다.
// mapper.word 의 접근 제한은 protected 로 설정해야 한다.
InOrder inOrder = inOrder(mapper.word, context);
// Text 클래스인 mapper.word 의 set 메소드가 먼저 호출됨을 검증한다.
inOrder.verify(mapper.word)
// eq 메소드를 통해 set 메소드의 인자로 전달되는 String 이 입력값과 동일한지 검증한다.
.set(eq("dog"));
// Context 클래스인 context 의 write 메소드가 다음으로 호출됨은 검증한다.
inOrder.verify(context)
// eq 메소드를 통해 set 메소드의 인자로 전달되는 Text(mapper.word)와 IntWritable 이 동일한지 검증한다
.write(eq(mapper.word), eq(new IntWritable(1)));
// 이후 반복
inOrder.verify(mapper.word).set(eq("dog"));
inOrder.verify(context).write(eq(mapper.word), eq(new IntWritable(1)));
inOrder.verify(mapper.word).set(eq("cat"));
inOrder.verify(context).write(eq(mapper.word), eq(new IntWritable(1)));
inOrder.verify(mapper.word).set(eq("owl"));
inOrder.verify(context).write(eq(mapper.word), eq(new IntWritable(1)));
}
@Test
public void wordCountReduceTestWithMockito1() throws IOException, InterruptedException {
/* GIVEN */
WordCount.IntSumReducer reducer = new WordCount.IntSumReducer();
Text key = new Text("dog");
List<IntWritable> values = Arrays.asList(new IntWritable(1), new IntWritable(1), new IntWritable(1));
Reducer.Context context = mock(Reducer.Context.class);
/* WHEN */
reducer.reduce(key, values, context);
/* THEN */
verify(context).write(eq(key), eq(new IntWritable(3)));
}
Java
복사
Counter Test
@Test
// Mockito 를 사용한 Counter 테스트
public void wordCountCounterTestWithMockito1() throws IOException, InterruptedException {
/* GIVEN */
WordCountWithCounter.TokenizeMapper mapper = new WordCountWithCounter.TokenizeMapper();
// Context 와 Counter 의 Mock 객체를 생성한다.
// Counter 의 최종 결과는 increment 메소드 호출 횟수를 통해 결정된다.
Mapper.Context context = mock(Mapper.Context.class);
Counter withCounter = mock(Counter.class);
Counter withoutCounter = mock(Counter.class);
LongWritable key = new LongWritable(0L);
Text value = new Text("dog dog cat owl! dog cow? cat owl! dog");
// context 의 getCounter 메소드가 호출되면 해당 counter 의 Mock 객체를 반환하도록 설정한다.
when(context.getCounter(WordCountWithCounter.Word.WITH_SPECIAL_CHARACTER))
.thenReturn(withCounter);
when(context.getCounter(WordCountWithCounter.Word.WITHOUT_SPECIAL_CHARACTER))
.thenReturn(withoutCounter);
/* WHEN */
mapper.map(key, value, context);
/* THEN */
// WITH_SPECIAL_COUNTER 의 increment 메소드는 해당 입력에서 총 3번 호출된다.
// increment 메소드가 호출될 때의 인자는 언제나 1로 동일하다.
verify(withCounter, times(3)).increment(1);
// WITHOUT_SPECIAL_COUNTER 의 increment 메소드는 해당 입력에서 총 6번 호출된다.
verify(withoutCounter, times(6)).increment(1);
}
Java
복사