들어가며.
프리코스 2주차 자동차 경주 게임을 TDD 로 개발하면서 출력과 입력 기능은 어떻게 테스트를 할 수 있는지에 대한 고민이 생겼고 이에 대한 자료와 해결 과정을 정리하고자 한다.
출력 기능 테스트.
설명하기에 앞서 출력 기능 클래스와 테스트 코드를 보겠습니다.
public class OutputViewer {
private OutputViewer() {
}
public static void getCarNameInputMessage() {
System.out.println(Constant.CAR_NAME_INPUT_STRING);
}
public static void getTryCountInputMessage() {
System.out.println(Constant.TRY_COUNT_INPUT_STRING);
}
}
public class OutputViewTest {
private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
@BeforeEach
void setUp() {
System.setOut(new PrintStream(outputStreamCaptor));
}
@Test
void 경주할_자동차_이름_안내문_출력() {
OutputViewer.getCarNameInputMessage();
assertThat(outputStreamCaptor.toString())
.contains("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분");
}
}
ByteArrayOutputStream 은 Java 의 OutputStream 을 상속받은 클래스로 데이터를 바이트 배열에 쓸 수 있도록 해주는 기능을 가지고 있습니다. 그래서 주로 출력 데이터를 바이트 배열로 캡처하고자 할 때 사용됩니다.
System.setOut(new PrintStream(...)) 메서드는 Java 의 표준 출력 스트림인 System.out 을 다른 출력 스트림으로 변경하는데 사용됩니다. 예를 들어, System.out.println() 을 다른 출력 스트림으로 리다이렉트할 때 사용됩니다.
입력 기능 테스트.
이 부분도 입력 기능 클래스와 테스트 코드 먼저 보여드리겠습니다.
public class InputReader {
private final InputParser inputParser;
public InputReader(InputParser inputParser) {
this.inputParser = inputParser;
}
public List<String> getNames() {
String input = read();
validateInputBlank(input);
return inputParser.toList(input);
}
private String read() {
String input = Console.readLine();
Console.close();
return input;
}
}
public class InputReaderTest {
private InputReader inputReader;
@BeforeEach
void setUp() {
inputReader = new InputReader(new InputParser());
}
private void setInput(String value) {
System.setIn(new ByteArrayInputStream(value.getBytes()));
}
@Test
void 시도할_횟수_입력값_반환() {
setInput("1");
assertEquals(inputReader.getTryCnt(), 1);
}
}
System.setIn(InputStream) 은 Java 의 표준 입력 스트림(System.in) 을 변경합니다. 기본적으로 System.in 은 콘솔 입력을 처리하는 InputStream 으로 이 메소드를 사용하여 다른 입력 소스로 리다이렉트할 수 있습니다.
ByteArrayInputStream(...) 은 주어진 바이트 배열을 기반으로 입력 스트림을 생성하고 이 스트림을 배열의 데이터를 읽을 수 있는 입력 스트림으로 사용됩니다.
위와 같은 원리로 Console.readLine() 이 상속 받고 있는 Scanner 에서 setInput() 으로 변경된 System.in 을 가져오게 되고 이를 사용하여 테스트에 사용할 수 있게 됩니다.
발생한 문제.
단일 테스트를 동작하면 생기지 않던 문제가 InputReaderTest 클래스 단위로 동작했을 시 NoSuchElementException 이 발생하였습니다.
문제를 찾던 중 Console 클래스가 Scanner 를 static 키워드로 선언하고 이로 인해 전역에서 공유가 되는 상태에서 싱글턴 패턴으로 구현하고 있다는 것을 알게 되었습니다. 이로 인해 여러 차례 호출되더라도 실제로 생성되는 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;
}
}
이를 해결하고자 위에 존재하는 close() 메소드를 사용하여 Scanner 를 초기화하였습니다.
'Trouble' 카테고리의 다른 글
[TEST] 우아한 프리코스 테스트 코드 알아보기 (0) | 2024.10.26 |
---|---|
[Git] 첫 번째 커밋 삭제 시 reset 으로 삭제하면 안 되는 이유 (0) | 2024.08.17 |
[Lombok] @Builder 정적 가져오기 문제 (0) | 2024.08.17 |
[QueryDSL] 컬렉션 fetchJoin 페이징 applying in memory (0) | 2024.08.08 |