簡體   English   中英

圍繞Sytem.in和System.out進行JUnit測試

[英]JUnit Testing around Sytem.in and System.out

我被要求在從命令行運行和操作的舊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()調用?

我建議重構代碼以允許注入輸入/輸出流,然后你可以模擬它們。 如果你想把它變成類似的東西

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);
  }
}

此設計可完成以下幾項工作:

  1. 它需要你的主類邏輯。 通常,主要方法實際上只是用於啟動應用程序。

  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();
}

我會重構Main,這樣更容易測試..就像這樣:

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;
   }
}

就個人而言,我嘗試遠離靜態變量。 如果你需要一個,你總是可以在上面的main方法中聲明它。

測試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());
    }
}

這是我使用的解決方案,不需要重構遺留代碼。

簡而言之,我創建了一個抽象測試類,它在一個單獨的線程上編譯和執行一個進程中的Application。 我將自己附加到進程的輸入/輸出並讀取/寫入它。

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);
}

優點

  • 不需要重構
  • 實際測試實現(無模擬/注入)
  • 不需要任何外部庫

缺點

  • 當事情無法正常工作時很難調試(可修復)
  • 嚴重依賴操作系統行為(此類中的Windows,但可修復)
  • 為每個測試類編譯應用程序(我認為可以修復嗎?)
  • 出現錯誤且進程未被殺死時“內存泄漏”(我認為可修復?)

這可能是一個邊緣“黑客”,但它滿足了我的需求和要求。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM