우테코 야구 숫자 게임을 구현하면서 단위 테스트 작성에 집중했다. 그러다 Scanner
, System.out
가 들어간 메서드의 테스트 구현에 문제가 발생하였다.
테스트를 하려는 메서드는 다음과 같은 형식이었다.
public void isMatchedTest() {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
/*중 략*/
System.out.println("...");
}
이 경우 일반적인 AssertJ를 사용할 수 없을 것이다. 입력인자가 입력되지 않아 리턴값 또한 배출하지 못할테니 말이다.
assertThat(isMatchedTest()).isTrue() --> 불가능
그래서 아래와 같이 해결하였다.
@Test
@DisplayName("플레이어의_숫자가_컴퓨터의_숫자와_같을때까지_힌트를_출력하고_일치하면_끝낸다")
void isMatchedTest() throws IOException {
String input = "125\n132\n123";
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
OutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
playGames.matchNumber(List.of(1,2,3));
assertThat(out.toString()).contains("2스트라이크","2볼 1스트라이크", "3스트라이크");
}
콘솔로부터 사용자의 input를 받을 때에는 Scanner이 필요하다. 그리고 Scanner를 생성할 때는 System.in을 넣어준다. 이 덕분에 Scanner는 콘솔로부터 사용자가 입력하는 값을 받아온다.
Scanner sc = new Scanner(System.in);
이 때 System.in
은 콘솔창에 사용자가 입력하는 값을 InputStream
으로 담는 역할을 한다. 그러면 Scanner
는 InputStream을 확인하여 사용자의 입력을 읽는다.
그러나 테스트를 할 때는 콘솔에 입력하는 대신 미리 InputStream
형식의 Mock데이터를 집어넣어 Scanner가
읽어내리게 해야 한다.
String input = "123";
InputStream in = new ByteArrayInputStream(input.getBytes());
System.setIn(in);
String input값을 ByteArrayInputStream
인자로 넘겨주어 InputStream
을 생성하면 콘솔에 입력하는 값을 대신할 수 있다.
System.out
은 System.in
의 반대로 움직인다고 생각하면 된다. System.out
은 PrintStream
으로 테스트 메서드가 채워나가야 하는 대상으로 처음부터 비어있는 상태에서 테스트를 진행하면서 PrintStream
이 출력한 내용대로 채워진다.
OutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
먼저 ByteArrayOutputStream()
을 인자로 하는 빈 PrintStream
을 생성하고 System.setOut(out)
으로 설정해준다. 테스트 실행시 모든 문구는 out
에 바이트 형태로 저장된다.
이후 out.toString()
으로 변환하여 비교하고자 하는 값과 비교하면 된다.
assertThat(out.toString()).contains("2스트라이크","2볼 1스트라이크", "3스트라이크");
테스트 결과
`
Reference :
https://stackoverflow.com/questions/31635698/junit-testing-for-user-input-using-scanner
https://stackoverflow.com/questions/1119385/junit-test-for-system-out-println/1119559#1119559
https://sakjung.tistory.com/33