2013-02-25 99 views
2

我被要求在從命令行運行和運行的遺留Java應用程序中引入單元測試。基本上主循環打印出一個菜單,用戶輸入內容並顯示更多數據。JUnit圍繞Sytem.in和System.out進行測試

這個主類說明了應用程序是如何工作的。

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()調用?

+2

如果您希望您的代碼在標準輸入上進行偵聽,同時也將測試代碼寫入標準輸入,那麼順序代碼將不夠用。我相信你需要兩個線程。一個用於Main.main(),另一個用於sendInput(「1」); – mjshaw 2013-02-25 22:05:43

+0

您是否正在測試reader.read()執行正確的操作,還是在傳遞'x'時while循環退出? – 2013-02-25 22:53:31

+1

我甚至會超越Threads,開始使用ProcessBuilder作爲自己的進程測試應用程序。這樣你就不需要擔心重定向任何東西 – radai 2013-02-25 23:02:37

回答

6

我會建議重構代碼以允許輸入/輸出流被注入,然後你可以嘲笑它們。如果你couuld它更改爲類似

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和系統.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(); 
} 
+0

這與使用System.setIn()和System.setOut()方法有什麼不同?我可以使用這些方法控制輸入和輸出,而不必重構10+個類,但是我似乎無法用reader.read()來覆蓋阻塞的IORead ... – Alexandre 2013-02-26 02:37:27

+0

@PeekaySwitch查看我的編輯。如果你不熟悉mock,有很多很好的java模擬庫,例如EasyMock,PowerMock和JMock。 – 2013-02-26 02:52:28

+1

恕我直言,這是一個嘲笑的濫用。在你的測試中使用一個真正的閱讀器,如StringReader。儘管如此,重構代碼的建議仍然是重點。 – NamshubWriter 2013-02-26 16:56:44

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

優點

  • 沒有重構需要
  • 實際測試的執行情況(無嘲諷/注射)
  • 不需要任何外部librairies

缺點

  • 相當困難的調試時,事情沒有proprely(可修復的)
  • 嚴重依賴操作系統的行爲(窗口在這個類,但可改正的)
  • 編譯爲每一個測試類(應用程序可固定工作的我覺得呢?)
  • 「內存泄漏」時,有一個錯誤,並且進程沒有中止 (可固定,我認爲?)

這可能是一個臨界「黑客」,但它滿足了我的需求和要求。