이번 과제에서는 함수를 분리하고, 각 함수별로 테스트를 작성하는 것에 익숙해지는 것을 목표로 하고 있다. 따라서 테스트 도구를 학습하기 위해 작성되어 있는 테스트를 분석해보았다.

 

과제를 간단하게 설명하자면, 야구 게임을 시작해서 랜덤 3자리 숫자를 생성한 뒤에, 사용자의 입력을 통하여 랜덤 숫자를 맞추는 게임이다.

같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.

 

  • 예) 상대방(컴퓨터)의 수가 425일 때
    • 123을 제시한 경우 : 1스트라이크
    • 456을 제시한 경우 : 1볼 1스트라이크
    • 789를 제시한 경우 : 낫싱

 

먼저 과제에 작성되어 있는 ApplicationTest.java 파일을 분석해보기로 했다.

 

1) 게임종료_후_재시작 테스트

@Test
void 게임종료_후_재시작() {
    assertRandomNumberInRangeTest(
            () -> {
                run("246", "135", "1", "597", "589", "2");
                assertThat(output()).contains("낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료");
            },
            1, 3, 5, 5, 8, 9
    );
}

run()은 Application.main()을 실행하고 System.setIn()을 통해 인자 값을 표준 입력 스트림으로 설정하도록 내부적으로 구현되어 있었다.

게임종료_후_재시작() 함수는 assertRandomNumberInRangeTest() 함수를 호출하는데, 이는 다음과 같이 구현되어 있었다.

public static void assertRandomNumberInRangeTest(
    final Executable executable,
    final Integer value,
    final Integer... values
) {
    assertRandomTest(
        () -> Randoms.pickNumberInRange(anyInt(), anyInt()),
        executable,
        value,
        values
    );
}

Executable은 잠재적으로 Throwable을 throws할 수 있는 코드 블럭을 구현하는데 사용될 수 있는 함수형 인터페이스다.

Runnable와 비슷하지만 Executable은 어느 종류의 exception이든 throw할 수 있다는점이 다르다.

 

assertRandomTest()의 구현은 다음과 같다.

 

private static <T> void assertRandomTest(
    final Verification verification,
    final Executable executable,
    final T value,
    final T... values
) {
    assertTimeoutPreemptively(RANDOM_TEST_TIMEOUT, () -> {
        try (final MockedStatic<Randoms> mock = mockStatic(Randoms.class)) {
            mock.when(verification).thenReturn(value, Arrays.stream(values).toArray());
            executable.execute();
        }
    });
}

mock은 가짜 객체를 의미한다. 실제 객체를 만들어 테스트를 하기에는 테스트를 하기엔 불필요한 요소들이 많기 때문에 우리가 필요한 부분만 포함하는 가짜 객체를 만들어 테스트하는 비용을 절약할 수 있다.

따라서, 위 코드에서는 Randoms의 mock 객체를 생성한다. mock객체는 verification을 호출하면 thenReturn() 괄호 안의 값을 리턴하도록 정의 되어있다. 그 이후 executable.execute()를 함으로써 assertRandomNumberInRangeTest의 executable이 실행되는 것이다. 아래의 코드는 assertRandomNumberInRangeTest의 executable 부분이다. 

run("246", "135", "1", "597", "589", "2");
assertThat(output()).contains("낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료");

즉, 맨 void 처음 게임종료_후_재시작() 테스트는 Application을 실행하여 입력값으로 run()내부의 인자를 입력하고, 그 결과의 출력이 올바른지 검증하는 테스트임을 알 수 있다.

 

 

2) 예외_테스트

@Test
void 예외_테스트() {
    assertSimpleTest(() ->
            assertThatThrownBy(() -> runException("1234"))
                    .isInstanceOf(IllegalArgumentException.class)
    );
}

Exception이 발생하는 경우를 테스트하기 위해서 assertThatThrownBy()를 사용한다.

protected final void runException(final String... args) {
    try {
        run(args);
    } catch (final NoSuchElementException ignore) {
    }
}

runException은 main을 실행하고 예외를 잡아낸다.

즉, 예외_테스트는 "1234"의 인자 값으로 main을 실행하고, 그 결과로 IllegalArgumentException을 throw했는지를 테스트한다.

+ Recent posts