Javaコマンドラインプログラムがあります。JUnitテストケースを作成して、System.in
。プログラムが実行されると、whileループに入り、ユーザーからの入力を待つためです。 JUnitでどのようにシミュレートしますか?
ありがとう
技術的にはSystem.in
、しかし一般的には、コードで直接呼び出すのではなく、アプリケーションの1つのポイントから入力ソースが制御されるように間接化の層を追加する方がより堅牢です。まさにそれを行う方法は実装の詳細です-依存性注入の提案は結構ですが、必ずしもサードパーティのフレームワークを導入する必要はありません。たとえば、呼び出しコードからI/Oコンテキストを渡すことができます。
切り替え方法System.in
:
String data = "Hello, World!\r\n";
InputStream stdin = System.in;
try {
System.setIn(new ByteArrayInputStream(data.getBytes()));
Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextLine());
} finally {
System.setIn(stdin);
}
@ McDowell's answer および System.outのテスト方法を示す別の回答 に基づいて、プログラムに入力を与え、その出力をテストするソリューションを共有したいと思います。
参考として、JUnit 4.12を使用します。
入力を出力に単純に複製するこのプログラムがあるとしましょう:
_import Java.util.Scanner;
public class SimpleProgram {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print(scanner.next());
scanner.close();
}
}
_
テストするには、次のクラスを使用できます。
_import static org.junit.Assert.*;
import Java.io.*;
import org.junit.*;
public class SimpleProgramTest {
private final InputStream systemIn = System.in;
private final PrintStream systemOut = System.out;
private ByteArrayInputStream testIn;
private ByteArrayOutputStream testOut;
@Before
public void setUpOutput() {
testOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(testOut));
}
private void provideInput(String data) {
testIn = new ByteArrayInputStream(data.getBytes());
System.setIn(testIn);
}
private String getOutput() {
return testOut.toString();
}
@After
public void restoreSystemInputOutput() {
System.setIn(systemIn);
System.setOut(systemOut);
}
@Test
public void testCase1() {
final String testString = "Hello!";
provideInput(testString);
SimpleProgram.main(new String[0]);
assertEquals(testString, getOutput());
}
}
_
コードは読みやすく、ソースを引用しているため、あまり説明しません。
JUnitがtestCase1()
を実行すると、ヘルパーメソッドが表示される順序で呼び出されます。
setUpOutput()
、_@Before
_注釈のためprovideInput(String data)
、testCase1()
から呼び出されますgetOutput()
、testCase1()
から呼び出されますrestoreSystemInputOutput()
、_@After
_注釈のため_System.err
_は必要なかったのでテストしませんでしたが、_System.out
_のテストと同様に、簡単に実装できるはずです。
これにアプローチする方法はいくつかあります。最も完全な方法は、模擬データをクラスに渡す偽のInputStreamであるテスト対象のクラスを実行しながら、InputStreamを渡すことです。コードでこれを多く行う必要がある場合は、依存性注入フレームワーク(Google Guiceなど)を見ることができますが、簡単な方法は次のとおりです。
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
テストでは、入力ストリームを受け取るコンストラクターを呼び出します。他のコードがそれを使用することを一般的に考慮しないように、そのコンストラクタパッケージをプライベートにし、テストを同じパッケージに配置することもできます。
dependency injection を使用するようにコードをリファクタリングしてください。 System.in
を直接使用するメソッドを作成する代わりに、メソッドにInputStream
を引数として受け入れます。次に、junitテストで、System.in
の代わりにテストInputStream
実装を渡すことができます。
システムルール ライブラリのTextFromStandardInputStream
ルールを使用して、コマンドラインインターフェイスの明確なテストを作成できます。
public void MyTest {
@Rule
public final TextFromStandardInputStream systemInMock
= emptyStandardInputStream();
@Test
public void readTextFromStandardInputStream() {
systemInMock.provideLines("foo");
Scanner scanner = new Scanner(System.in);
assertEquals("foo", scanner.nextLine());
}
}
完全な開示:私はそのライブラリの著者です。
カスタムInputStream
を作成できます System
クラスに添付します
_class FakeInputStream extends InputStream {
public int read() {
return -1;
}
}
_
そして、あなたのScanner
でそれを使用します
System.in = new FakeInputStream();
前:
_InputStream in = System.in;
...
Scanner scanner = new Scanner( in );
_
後:
_InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );
_
クラスが入力ストリームから読み取られたデータを実際にどのように読み取るかではなく、クラスでどのように機能するかをテストする方が良いと思いますが。
BufferedReader.readLine()
の問題は、ユーザー入力を待つブロッキングメソッドであるということです。あなたは特にそれをシミュレートしたくない(つまり、テストを高速にしたい)ように思えます。しかし、テストコンテキストでは、テスト中にnull
を継続的に高速で返しますが、これは面倒です。
純粋主義者のために、パッケージの下にgetInputLine
を作り、それをock笑することができます:easy-peezy。
String getInputLine() throws Exception {
return br.readLine();
}
...アプリとのユーザーインタラクションのループを(通常)停止する方法があることを確認する必要があります。また、何らかの方法でモックのdoReturn
を変更するまで、「入力行」は常に同じであるという事実に対処する必要があります。これはほとんどユーザー入力ではありません。
自分自身の生活を楽にする(そして読みやすいテストを作成する)ことを望む非純粋主義者のために、あなたはアプリのコードに以下のすべてを置くことができます:
private Deque<String> inputLinesDeque;
void setInputLines(List<String> inputLines) {
inputLinesDeque = new ArrayDeque<String>(inputLines);
}
private String getInputLine() throws Exception {
if (inputLinesDeque == null) {
// ... i.e. normal case, during app run: this is then a blocking method
return br.readLine();
}
String nextLine = null;
try {
nextLine = inputLinesDeque.pop();
} catch (NoSuchElementException e) {
// when the Deque runs dry the line returned is a "poison pill",
// signalling to the caller method that the input is finished
return "q";
}
return nextLine;
}
...テストでは、次のようになります。
consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));
入力行を必要とするこの「ConsoleHandler」クラスのメソッドをトリガーする前。
たぶんこのような(テストされていない):
InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));
in.write("text".getBytes("utf-8"));
System.setIn( save_in );
より多くの部品:
//PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));
InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));
//start something that reads stdin probably in a new thread
// Thread thread=new Thread(new Runnable() {
// @Override
// public void run() {
// CoursesApiApp.main(new String[]{});
// }
// });
// thread.start();
//maybe wait or read the output
// for(int limit=0; limit<60 && not_ready ; limit++)
// {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
in.write("text".getBytes("utf-8"));
System.setIn( save_in );
//System.setOut(save_out);