Java 콘솔 입력 테스트하기 + 트러블 슈팅

yesjuhee·2024년 10월 31일

우아한테크코스

목록 보기
4/5

우테코 프리코스 2주차 자동차 경주 미션을 하면서 콘솔 입력 테스트를 공부하고 문제해결한 과정을 정리해봤습니다.

우테코 Console 클래스

package camp.nextstep.edu.missionutils;

import java.util.Scanner;

public class Console {
    private static Scanner scanner;

    private Console() {
    }

    public static String readLine() {
        return getInstance().nextLine();
    }

    public static void close() {
        if (scanner != null) {
            scanner.close();
            scanner = null;
        }
    }

    private static Scanner getInstance() {
        if (scanner == null) {
            scanner = new Scanner(System.in);
        }
        return scanner;
    }
}

미션을 하면서 입력을 받기 위해 우테코에서 제공하는 Console 클래스의 readLine() 메서드를 사용했습니다. 해당 메서드는 내부적으로 입력 스트림을 생성자에 Scanner 인스턴스를 생성해서 nextLine() 메서드를 통해 입력 값을 받아오는 것을 확인할 수 있습니다.

입력값 테스트하기

@Test
void 자동차이동횟수입력_테스트() {
    // given
    String input = "5";
    System.setIn(new ByteArrayInputStream(input.getBytes()));

    // when
    int totalRaceCount = InputHandler.getTotalRaceCount();

    // then
    assertThat(totalRaceCount).isEqualTo(Integer.parseInt(input));
}

정수 형태의 문자열을 입력받아서 검증 후 반환하는 InputHandelr.getTotalRaceCount() 메서드를 위한 간단한 테스트코드입니다. 위의 코드에서 볼 수 있듯이 System.setIn(new ByteArrayInputStream(input.getBytes()));를 사용하여 입력 값을 세팅해줄 수 있습니다.

이전의 Console에서 스캐너의 인스턴스를 생성할 때 System.in을 넘겨주었습니다. 즉 Console 클래스를 통해 표준 입력 스트림(System.in)의 값을 읽어올 것이라는 건데요, 테스트 코드에서는 System.setIn을 이용해 원하는 값을 표준 입력 스트림에 세팅해줄 수 있습니다.

Trouble Shooting

콘솔 입력 테스트를 여러개 할 때 NoSuchElementException 발생

java.util.NoSuchElementException: No line found

	at java.base/java.util.Scanner.nextLine(Scanner.java:1660)
	at camp.nextstep.edu.missionutils.Console.readLine(Console.java:12)
	at racingcar.InputHandler.getCarNames(InputHandler.java:10)
	at racingcar.InputHandlerTest.자동차_이름_1명_입력_테스트(InputHandlerTest.java:40)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

위에서 작성한 테스트코드를 그대로 사용하면 하나의 테스트만 실행할 때는 문제 없이 작동하지만, 여러개의 테스트를 동시에 진행하려고 하니 위와 같이 NoSuchElementException: No line found와 같이 에러가 발생합니다.

@AfterEach
void tearDown() {
    Console.close();
}

@Test
void 자동차이동횟수입력_테스트() {
    // given
    String input = "5";
    System.setIn(new ByteArrayInputStream(input.getBytes()));

    // when
    int totalRaceCount = InputHandler.getTotalRaceCount();

    // then
    assertThat(totalRaceCount).isEqualTo(Integer.parseInt(input));
}

해당 오류를 해결하기 위해서는 위와 같이 각 테스트가 끝나고 Console.close()를 통해 생성했던 스캐너를 close 해야합니다.

만약 Console.close()를 하지 않으면 이전 스캐너가 닫히지 않은 상태로 다시 테스트를 실행할 때 이미 값을 소진한 이전 입력 스트림에서 값을 읽어오려고 하다가 NoSuchElemtentException: No line found가 발생합니다. 따라서 Console.close()로 스캐너를 닫아준 후 System.setIn()을 해줘야 의도한대로 입력을 할 수 있습니다.

null 값을 테스트 할 때 NullPointerException

입력에 대한 예외 처리를 테스트하는 과정에서 null 입력을 테스트하려고 처음에 아래와 같이 코드를 작성했습니다.

String givenName = null;
System.setIn(new ByteArrayInputStream(givenName.getBytes()));

위처럼 코드를 작성하니 givenName.getBytes()에서 NullPointerException일 발생해서 사용할 수 없었습니다.

다른 테스트 방법을 찾기 위해 아래의 우테코가 제공하는 command() 코드를 활용해서 테스트를 작성해봤습니다. 이 테스트의 작동 결과는 어떻게 될까요?

private void command(final String... args) {
    final byte[] buf = String.join("\n", args).getBytes();
    System.setIn(new ByteArrayInputStream(buf));
}

@Test
void 자동차이름입력_예외테스트_입력없음(String input) {
    // give
    String givenName = null;
    command(null);

    // when & then
    assertThatThrownBy(InputHandler::getCarNames)
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("경주할 자동차 이름을 입력하세요.");
}

이번에는 예상과 달리 IllegalArgumentException이 발생하지 않았습니다. 즉 예상과 달리 null 값이 입력되지 않은 것이죠.

엇 그럼 null 대신 어떤 값이 입력된걸까요? 궁금증을 해결하기 위해 아래처럼 테스트를 실행시켜봤습니다.

assertThat(String.join("\n", input)).isEqualTo(null);   // 1
assertThat(String.join("\n", input)).isEqualTo("");     // 2
assertThat(String.join("\n", input)).isEqualTo("null"); // 3

3개의 테스트 중 어느 것이 통과됐을까요..?

두구두구두구
...
..
.
정답은 3번이었습니다.

즉 테스트를 할 때 입력값으로 null을 넘겨 주어도 이 값이 null로 입력되는게 아니라 그냥 문자열 취급이 되는 것이었죠. 이 과정을 통해서 콘솔 입력 테스트를 할 때 null 입력은 배제한다는 결론을 내렸습니다. 실제 사용자가 null을 입력하는건 사실상 불가능하기도 한 점과 연결이 된다고 생각합니다.

profile
https://yesjuhee.tistory.com/

0개의 댓글