현재 Java 를 공부중이고 조금 더 빠르게 익숙해지기 위해 우아한테크코스 문제들을 풀어보고 있다. 이 안에 각 주차 요구사항에는 테스트코드에 대한 요구도 있었는데 개인적으로 이 테스트코드가 어떻게 돌아가는지 궁금해서 한번 뜯어보려 한다.
@Test
void 기능_테스트() {
assertRandomUniqueNumbersInRangeTest(
() -> {
run("8000", "1,2,3,4,5,6", "7");
assertThat(output()).contains(
"8개를 구매했습니다.",
"[8, 21, 23, 41, 42, 43]",
"[3, 5, 11, 16, 32, 38]",
"[7, 11, 16, 35, 36, 44]",
"[1, 8, 11, 31, 41, 42]",
"[13, 14, 16, 38, 42, 45]",
"[7, 11, 30, 40, 42, 43]",
"[2, 13, 22, 32, 38, 45]",
"[1, 3, 5, 14, 22, 45]",
"3개 일치 (5,000원) - 1개",
"4개 일치 (50,000원) - 0개",
"5개 일치 (1,500,000원) - 0개",
"5개 일치, 보너스 볼 일치 (30,000,000원) - 0개",
"6개 일치 (2,000,000,000원) - 0개",
"총 수익률은 62.5%입니다."
);
},
List.of(8, 21, 23, 41, 42, 43),
List.of(3, 5, 11, 16, 32, 38),
List.of(7, 11, 16, 35, 36, 44),
List.of(1, 8, 11, 31, 41, 42),
List.of(13, 14, 16, 38, 42, 45),
List.of(7, 11, 30, 40, 42, 43),
List.of(2, 13, 22, 32, 38, 45),
List.of(1, 3, 5, 14, 22, 45)
);
}
대표적인 기능 테스트 코드 중 하나였다. 그냥 크게 assertRandomUniqueNumbersInRangeTest() 함수 하나로 이뤄지고 이 함수에 매개변수들을 전달하여 테스트를 진행하는건가 라고 추측하고 있다. 그러면 이 assertRandom...Test() 로 들어가보자.
public static void assertRandomUniqueNumbersInRangeTest(Executable executable, List<Integer> value, List<Integer>... values) {
assertRandomTest(() -> {
Randoms.pickUniqueNumbersInRange(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt());
}, executable, value, values);
}
먼저 각 매개변수들을 파악해보자.
그리고 이 함수에서도 다시 assertRandomTest 라는 함수가 실행된다.
private static <T> void assertRandomTest(MockedStatic.Verification verification, Executable executable, T value, T... values) {
org.junit.jupiter.api.Assertions.assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> {
MockedStatic<Randoms> mock = Mockito.mockStatic(Randoms.class);
Throwable var5 = null;
try {
mock.when(verification).thenReturn(value, Arrays.stream(values).toArray());
executable.execute();
} catch (Throwable var14) {
var5 = var14;
throw var14;
} finally {
if (mock != null) {
if (var5 != null) {
try {
mock.close();
} catch (Throwable var13) {
var5.addSuppressed(var13);
}
} else {
mock.close();
}
}
}
});
}
이 부분이 복잡했다. 이 부분도 각 매개변수 먼저 집고 넘어가보자. 사실 첫번째 매개변수를 제외하고는 위의 함수와 동일하다.
첫번째 변수 verification 은 Randoms.pickNumbersInRange() 함수를 실행시키는 람다함수이다. 자료형에 대해서는 나중에 더 알아보고 먼저 org.junit. ... 이 함수 부터 보자.
assertTimeOutPreemtively(Duration , Executable)
위 함수는 Duration 내에 executable 이 실행이 되는지 확인한다. 위에서 Executable 에 해당하는 함수를 살펴보면
MockedStatic<Randoms> mock = Mockito.mockStatic(Randoms.class);
그리고 이후 try 안에서 아래를 만나게 된다.
mock.when(verification).thenReturn(value, Arrays.stream(values).toArray());
테스트코드를 뜯어봐야겠다고 생각한 이유가 이 부분이 궁금해서였다. 테스트코드를 실행하면서 생겼던 의문이 "지금 Random class 의 함수들을 이용해 random 하게 값들을 제공받고 있는데 어떻게 고정된 값을 반환시킬 수 있는거지?" 하는 점이었다. 이 부분을 통해서 어떻게 그 부분이 가능했는지 알 수 있었다.
이 부분은 mocking 한 객체 (Randoms) 에서 verification 함수의 호출하기를 기다린다. 그리고 테스트 과정에서 이 함수가 호출되면 매개변수로 전달된 값 들을 반환한다.
즉, 현재 verification 으로 전달된 Randoms.pickUniqueNumberInRange 함수가 전달되면, 그 다음 매개변수로 전달된 array 와 arrays 를 리턴해준다.
그리고 그 이후
executable.execute();
executable 로 전달된 함수를 실행한다. 즉 이 부분이 직접 테스트 코드를 실행시키는 부분이고 이전 부분은 테스트코드를 실행하기 위한 준비단계이다.
catch (Throwable var14) {
var5 = var14;
throw var14;
}
만약 Throwable 객체 , 즉 Error 나 Exception 이 발생하면 var5 에 var 14 를 대입하고 이를 던진다.
이후 try 가 끝나면 두 가지로 나뉘어진다. 여기서
mock.close()
위 코드에 대해 먼저 알아보자. mock.close() 는 mock.mockStatic 으로 mocking 된 객체의 동작을 가로채는 작업을 원래 상태로 되돌리는 작업이다. 테스트가 끝나면 해당 객체의 동작이 원래대로 되돌려져야 하기 때문에 해당 메스드를 사용한다.
다시 돌아와서 try 가 끝난 이후를 살펴보면
mocking 이 된 경우 var5 가 null 이 아닌경우, (에러나 예외 발생 X)
var5.addSuppressed(var13);
var13 Throwable 이 무시되지 않도록 addSurpressed 를 이용하여 var5 와 함께 해당 에러나 예외를 함께 기록한다.
mocking 이 되었는데 에러 발생하지 않았으면 그대로 종료