2

コマンド ラインから実行および動作するレガシー Java アプリケーションに単体テストを導入するよう依頼されました。基本的に、メイン ループはメニューを出力し、ユーザーが何かを入力すると、さらにデータが表示されます。

この Main クラスは、アプリケーションがどのように機能するかを示しています。

public class Main{

    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    public static void main(String argv[]) throws IOException{
        while (true) {
            char input = (char) reader.read();

            if(input == 'x'){
                return;
            }

            System.out.println(input);
        }
    }
}

テストメソッドを次のようにしたい

public void testCaseOne(){
    Main.main();
    String result = "";

    result = sendInput("1");
    assertEqual(result, "1");

    result = sendInput("x");
    assertEqual(result,"");
}

System.setOut()およびメソッドについては認識していますが、メソッドがスレッドをブロックしているため、このコンテキストでメソッドを機能さSystem.setIn()せる方法がわかりません。System.setIn()reader.read()

私のテスト設計は間違っていますか?sendInput()ブロッキング Reader.read() 呼び出しを介して動作するようにメソッドを設計する方法はありますか?

4

3 に答える 3

6

入力/出力ストリームを注入できるようにコードをリファクタリングすることをお勧めします。その後、それらをモックできます。のようなものに変更できれば

public class Main{

    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    public static void main(String argv[]) throws IOException{
        new YourClass(reader,System.out).run();
    }
}

public class YourClass { // I don't know what your class is actually doing, but name it something appropriate
  private final InputReader reader;
  private final PrintStream output;

  public YourClass(InputReader reader, PrintStream output) {
       this.reader = reader;
       this.output = ouptut;
  }

  public void run() {

        while (true) {
        char input = (char) reader.read();

        if(input == 'x')
            return;

        output.println(input);
  }
}

この設計では、次の 2 つのことを行います。

  1. メインクラスからロジックを取り出します。通常、main メソッドは、アプリケーションを起動するためだけに使用されます。

  2. これによりYourClass、単体テストがより簡単になります。テストでは、入力/出力を単純にモックできます。

編集:このリファクタリングがブロッキングIOの問題にどのように役立つかを更新してください

上記のようにリーダー/出力を注入可能にすることで、実際には実際の System.in と System.out を使用する必要がなくなり、代わりにモックを使用できます。これにより、実際にブロッキング読み取りを行う必要がなくなります。

public void testCaseOne(){
    // pseudocode for the mock - this will vary depending on your mock framework
    InputReader reader = createMock(InputReader);
    // the first time you read it will be a "1", the next time it will be an "x"
    expect(reader.read()).andReturn("1");
    expect(reader.read()).andReturn("x");

    PrintStream stream = createMock(PrintStream);
    // only expect the "1" to get written. the "x" is the exit signal
    expect(stream.println("1"));

    new YourClass(reader,stream).run();
    verifyMocks();
}
于 2013-02-25T23:02:43.383 に答える
1

メインをリファクタリングして、テストしやすくします..次のようにします。

public class Main{

    private boolean quit = false;

    public static void main(String[] argv) throws IOException {
        Main main = new Main();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        char input = main.readInput(reader);
        while (!main.quit()) {
            System.out.println(input);
            input = main.readInput(reader);
        }
    }

    public char readInput(Reader reader) throws IOException{
        char input = (char) reader.read();

        if(input == 'x'){
            quit = true;
            return '\0';
        }

        return input;
    }

    public boolean quit(){
        return quit;
   }
}

個人的には、静的変数には近づかないようにしています。必要な場合は、上記のようにメイン メソッドでいつでも宣言できます。

while(true) のテストは、while ループが終了しないかどうかのテストに無限の時間がかかるため、ほとんど不可能です。次に、その場合にループの終了をテストする必要があるかどうかという問題がありますmain.quit() == true。個人的には、コア ロジックのみをテストし、残りはテストしないままにします。

public class MainTest {

    private Main main;

    @Before
    public void setup(){
        main = new Main();
    }

    @Test
    public void testCaseOne() throws IOException{

        char result1 = main.readInput(new StringReader("1"));
        assertEquals(result1, '1');
        assertFalse(main.quit());

        char result2 = main.readInput(new StringReader("x"));
        assertEquals(result2, '\0');
        assertTrue(main.quit());
    }
}
于 2013-02-25T23:06:13.470 に答える
0

これは、レガシーコードのリファクタリングを必要としない、私が行ったソリューションです。

簡単に言えば、別のスレッドでプロセス内のアプリケーションをコンパイルして実行する抽象テスト クラスを作成しました。私はプロセスの入力/出力に接続し、それに読み書きします。

public abstract class AbstractTest extends TestCase{

    private Process process;
    private BufferedReader input;
    private BufferedWriter output;

    public AbstractTest() {
        //Makes a text file with all of my .java files for the Java Compiler process
        Process pDir = new ProcessBuilder("cmd.exe", "/C", "dir /s /B *.java > sources.txt").start();
        pDir.waitFor();

        //Compiles the application
        Process p = new ProcessBuilder("cmd.exe", "/C", "javac @sources.txt").start();
        p.waitFor();
    }


    protected void start(){
        Thread thread = new Thread() {
            public void run() {
                //Execute the application
                String command = "java -cp src/main packagename.Main ";
                AbstractTest.this.process = = new ProcessBuilder("cmd.exe", "/C", command).start();
                AbstractTest.this.input = new BufferedReader(new InputStreamReader(AbstractTest.this.process.getInputStream()));
                AbstractTest.this.output = new BufferedWriter(new OutputStreamWriter(AbstractTest.this.process.getOutputStream()));
            }
        }
    }

    protected String write(String data) {
         output.write(data + "\n");
         output.flush();
         return read();
    }

    protected String read(){
         //use input.read() and read until it makes senses
    }

    protected void tearDown() {
        this.process.destroy();
        this.process.waitFor();
        this.input.close();
        this.output.close();
    }

}

その後、実際のテスト クラスを作成し、実際のテスト メソッドを実装するのは非常に簡単でした。

public void testOption3A(){
    start();
    String response = write("3");
    response = write("733");
    assertEquals("*** Cactus ID 733 not found ***",response);
}

長所

  • リファクタリングは不要
  • 実際に実装をテストする (No Mocking/Injection)
  • 外部ライブラリは必要ありません

短所

  • 物事が適切に機能していないときにデバッグするのはかなり難しい (修正可能)
  • OS の動作に大きく依存 (Windows はこのクラスですが、修正可能)
  • すべてのテスト クラスのアプリケーションをコンパイルします (修正可能だと思いますか?)
  • エラーが発生し、プロセスが強制終了されない場合の「メモリ リーク」(修正可能だと思いますか?)

これはおそらく境界線上の「ハック」ですが、私のニーズと要求を満たしました。

于 2013-03-07T06:57:10.563 に答える