Java 테스트코드 분석 (우아한테크코스 week3)

이광훈·2024년 1월 14일

개요

현재 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);
    }

먼저 각 매개변수들을 파악해보자.

  1. Executable 은 맨 처음 전달된 람다함수
  2. 나머지 배열들은 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();
                    }
                }

            }

        });
    }

이 부분이 복잡했다. 이 부분도 각 매개변수 먼저 집고 넘어가보자. 사실 첫번째 매개변수를 제외하고는 위의 함수와 동일하다.

  1. 2번째 변수 executable 은 맨 처음 전달된 람다함수
  2. 3,4 번째 변수도 맨 처음 전달된 배열들이다.

첫번째 변수 verification 은 Randoms.pickNumbersInRange() 함수를 실행시키는 람다함수이다. 자료형에 대해서는 나중에 더 알아보고 먼저 org.junit. ... 이 함수 부터 보자.

assertTimeOutPreemtively(Duration , Executable)

위 함수는 Duration 내에 executable 이 실행이 되는지 확인한다. 위에서 Executable 에 해당하는 함수를 살펴보면

MockedStatic<Randoms> mock = Mockito.mockStatic(Randoms.class);
  • 가장 먼저 해당 코드를 만나게된다. 여기서 Mockito 는 Java 에서 Unit 테스트를 할 때 객체의 mocking 을 도와주는 프레임워크이다. 즉 위 코드의 의미는 Randoms class 를 mocking 하는 과정이다.

그리고 이후 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 가 끝난 이후를 살펴보면

  1. mocking 이 된 경우 var5 가 null 이 아닌경우, (에러나 예외 발생 X)

    • mock.close() 실행
    • 만약 과정에서 에러나 예외가 발생하면
    var5.addSuppressed(var13);

    var13 Throwable 이 무시되지 않도록 addSurpressed 를 이용하여 var5 와 함께 해당 에러나 예외를 함께 기록한다.

  2. mocking 이 되었는데 에러 발생하지 않았으면 그대로 종료

profile
허허,,,

0개의 댓글