web-dev-qa-db-ja.com

JUnit:System.inテストのシミュレーション方法

Javaコマンドラインプログラムがあります。JUnitテストケースを作成して、System.in。プログラムが実行されると、whileループに入り、ユーザーからの入力を待つためです。 JUnitでどのようにシミュレートしますか?

ありがとう

48
Noppanit

技術的には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);
}
61
McDowell

@ 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()を実行すると、ヘルパーメソッドが表示される順序で呼び出されます。

  1. setUpOutput()、_@Before_注釈のため
  2. provideInput(String data)testCase1()から呼び出されます
  3. getOutput()testCase1()から呼び出されます
  4. 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;
     }
 }

テストでは、入力ストリームを受け取るコンストラクターを呼び出します。他のコードがそれを使用することを一般的に考慮しないように、そのコンストラクタパッケージをプライベートにし、テストを同じパッケージに配置することもできます。

8
Yishai

dependency injection を使用するようにコードをリファクタリングしてください。 System.inを直接使用するメソッドを作成する代わりに、メソッドにInputStreamを引数として受け入れます。次に、junitテストで、System.inの代わりにテストInputStream実装を渡すことができます。

5
Asaph

システムルール ライブラリの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());
  }
}

完全な開示:私はそのライブラリの著者です。

4
Stefan Birkner

カスタム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 );
_

クラスが入力ストリームから読み取られたデータを実際にどのように読み取るかではなく、クラスでどのように機能するかをテストする方が良いと思いますが。

1
OscarRyz

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」クラスのメソッドをトリガーする前。

1
mike rodent

たぶんこのような(テストされていない):

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);
0
Shimon Doodkin