사용자 입력이 필요한 메서드에 대한 일부 JUnit 테스트를 만들려고합니다. 테스트중인 방법은 다음 방법과 비슷합니다.
public static int testUserInput() {
Scanner keyboard = new Scanner(System.in);
System.out.println("Give a number between 1 and 10");
int input = keyboard.nextInt();
while (input < 1 || input > 10) {
System.out.println("Wrong number, try again.");
input = keyboard.nextInt();
}
return input;
}
나 또는 다른 사람이 JUnit 테스트 메서드에서 수동으로 수행하는 대신 프로그램을 자동으로 int로 전달할 수있는 방법이 있습니까? 사용자 입력 시뮬레이션처럼?
미리 감사드립니다.
답변
System.setIn (InputStream in)을 호출 하여 System.in을 자신의 스트림으로 바꿀 수 있습니다 . InputStream은 바이트 배열 일 수 있습니다.
InputStream sysInBackup = System.in; // backup System.in to restore it later
ByteArrayInputStream in = new ByteArrayInputStream("My string".getBytes());
System.setIn(in);
// do your thing
// optionally, reset System.in to its original
System.setIn(sysInBackup);
IN과 OUT을 매개 변수로 전달하면 다른 접근 방식을 사용하여이 메서드를 더 테스트 할 수 있습니다.
public static int testUserInput(InputStream in,PrintStream out) {
Scanner keyboard = new Scanner(in);
out.println("Give a number between 1 and 10");
int input = keyboard.nextInt();
while (input < 1 || input > 10) {
out.println("Wrong number, try again.");
input = keyboard.nextInt();
}
return input;
}
답변
코드를 테스트하려면 시스템 입력 / 출력 함수에 대한 래퍼를 만들어야합니다. 종속성 주입을 사용하여이를 수행 할 수 있으며 새 정수를 요청할 수있는 클래스를 제공합니다.
public static class IntegerAsker {
private final Scanner scanner;
private final PrintStream out;
public IntegerAsker(InputStream in, PrintStream out) {
scanner = new Scanner(in);
this.out = out;
}
public int ask(String message) {
out.println(message);
return scanner.nextInt();
}
}
그런 다음 모의 프레임 워크 (Mockito 사용)를 사용하여 함수에 대한 테스트를 만들 수 있습니다.
@Test
public void getsIntegerWhenWithinBoundsOfOneToTen() throws Exception {
IntegerAsker asker = mock(IntegerAsker.class);
when(asker.ask(anyString())).thenReturn(3);
assertEquals(getBoundIntegerFromUser(asker), 3);
}
@Test
public void asksForNewIntegerWhenOutsideBoundsOfOneToTen() throws Exception {
IntegerAsker asker = mock(IntegerAsker.class);
when(asker.ask("Give a number between 1 and 10")).thenReturn(99);
when(asker.ask("Wrong number, try again.")).thenReturn(3);
getBoundIntegerFromUser(asker);
verify(asker).ask("Wrong number, try again.");
}
그런 다음 테스트를 통과하는 함수를 작성하십시오. 요청 / 받기 정수 중복을 제거 할 수 있고 실제 시스템 호출이 캡슐화되기 때문에 함수가 훨씬 더 깔끔합니다.
public static void main(String[] args) {
getBoundIntegerFromUser(new IntegerAsker(System.in, System.out));
}
public static int getBoundIntegerFromUser(IntegerAsker asker) {
int input = asker.ask("Give a number between 1 and 10");
while (input < 1 || input > 10)
input = asker.ask("Wrong number, try again.");
return input;
}
이것은 당신의 작은 예제에 대해 과잉처럼 보일 수 있지만, 이와 같이 개발하는 더 큰 애플리케이션을 구축하는 경우 다소 빨리 결과를 얻을 수 있습니다.
답변
유사한 코드를 테스트하는 일반적인 방법 중 하나는 이 StackOverflow 답변 과 유사한 Scanner 및 PrintWriter를받는 메서드를 추출 하고 다음을 테스트하는 것입니다.
public void processUserInput() {
processUserInput(new Scanner(System.in), System.out);
}
/** For testing. Package-private if possible. */
public void processUserInput(Scanner scanner, PrintWriter output) {
output.println("Give a number between 1 and 10");
int input = scanner.nextInt();
while (input < 1 || input > 10) {
output.println("Wrong number, try again.");
input = scanner.nextInt();
}
return input;
}
끝까지 출력을 읽을 수 없으며 모든 입력을 미리 지정해야합니다.
@Test
public void shouldProcessUserInput() {
StringWriter output = new StringWriter();
String input = "11\n" // "Wrong number, try again."
+ "10\n";
assertEquals(10, systemUnderTest.processUserInput(
new Scanner(input), new PrintWriter(output)));
assertThat(output.toString(), contains("Wrong number, try again.")););
}
물론 오버로드 메서드를 만드는 대신 “스캐너”및 “출력”을 테스트중인 시스템의 변경 가능한 필드로 유지할 수도 있습니다. 나는 가능한 한 무국적 수업을 유지하는 것을 좋아하지만, 그것이 당신이나 당신의 동료 / 강사에게 중요하다면 큰 양보가 아닙니다.
또한 테스트중인 코드와 동일한 Java 패키지에 테스트 코드를 넣도록 선택할 수도 있습니다 (다른 소스 폴더에 있더라도). 이렇게하면 두 매개 변수 오버로드의 가시성을 패키지 전용으로 완화 할 수 있습니다.
답변
더 간단한 방법을 찾았습니다. 그러나 @Stefan Birkner의 외부 라이브러리 System.rules 를 사용해야합니다.
나는 거기에 제공된 예제를 가져 왔고 더 간단해질 수는 없다고 생각합니다.
import java.util.Scanner;
public class Summarize {
public static int sumOfNumbersFromSystemIn() {
Scanner scanner = new Scanner(System.in);
int firstSummand = scanner.nextInt();
int secondSummand = scanner.nextInt();
return firstSummand + secondSummand;
}
}
테스트
import static org.junit.Assert.*;
import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.TextFromStandardInputStream;
public class SummarizeTest {
@Rule
public final TextFromStandardInputStream systemInMock
= emptyStandardInputStream();
@Test
public void summarizesTwoNumbers() {
systemInMock.provideLines("1", "2");
assertEquals(3, Summarize.sumOfNumbersFromSystemIn());
}
}
그러나 문제는 내 경우 두 번째 입력에 공백이 있으며 전체 입력 스트림이 null이됩니다!
답변
키보드에서 숫자를 검색하는 논리를 자체 메서드로 추출하여 시작할 수 있습니다. 그런 다음 키보드에 대한 걱정없이 유효성 검사 논리를 테스트 할 수 있습니다. keyboard.nextInt () 호출을 테스트하기 위해 모의 객체 사용을 고려할 수 있습니다.
답변
콘솔을 시뮬레이션하기 위해 stdin에서 읽기에 대한 문제를 수정했습니다.
내 문제는 특정 개체를 생성하기 위해 콘솔을 JUnit 테스트에서 작성하고 싶습니다 …
문제는 당신이 말하는 모든 것과 같습니다. JUnit 테스트에서 Stdin으로 어떻게 쓸 수 있습니까?
그런 다음 대학에서 System.setIn (InputStream) stdin filedescriptor를 변경하고 다음과 같이 쓸 수 있다고 말하는 것과 같은 리디렉션에 대해 배웁니다.
하지만 수정해야 할 문제가 하나 더 있습니다 … JUnit 테스트 블록이 새 InputStream에서 읽기를 기다리고 있으므로 InputStream에서 읽을 스레드를 만들고 새 Stdin에서 JUnit 테스트 스레드를 작성해야합니다. 먼저해야 할 일 나중에 stdin에서 읽을 스레드를 작성하면 경쟁 조건이 생길 가능성이 있기 때문에 Stdin에 작성하십시오 … 읽기 전에 InputStream에 쓸 수 있거나 쓰기 전에 InputStream에서 읽을 수 있습니다 …
이것은 내 코드이고, 내 영어 실력이 나쁩니다. JUnit 테스트에서 stdin으로 쓰기를 시뮬레이션하는 문제와 솔루션을 이해할 수 있기를 바랍니다.
private void readFromConsole(String data) throws InterruptedException {
System.setIn(new ByteArrayInputStream(data.getBytes()));
Thread rC = new Thread() {
@Override
public void run() {
study = new Study();
study.read(System.in);
}
};
rC.start();
rC.join();
}
답변
java.io.Console과 유사한 메서드를 정의하는 인터페이스를 만든 다음이를 System.out에 읽거나 쓰는 데 사용하는 것이 유용하다는 것을 알았습니다. 실제 구현은 System.console ()에 위임되지만 JUnit 버전은 미리 준비된 입력 및 예상 응답이있는 모의 객체 일 수 있습니다.
예를 들어, 사용자의 미리 준비된 입력을 포함하는 MockConsole을 생성합니다. 모의 구현은 readLine이 호출 될 때마다 목록에서 입력 문자열을 팝합니다. 또한 응답 목록에 기록 된 모든 출력을 수집합니다. 테스트가 끝날 때 모든 것이 잘 되었다면 모든 입력을 읽었을 것이며 출력에 대해 주장 할 수 있습니다.