[英]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);
}
}
此設計可完成以下幾項工作:
它需要你的主類邏輯。 通常,主要方法實際上只是用於啟動應用程序。
它使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);
}
優點
缺點
這可能是一個邊緣“黑客”,但它滿足了我的需求和要求。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.