【问题标题】:Testing console based applications/programs - Java测试基于控制台的应用程序/程序 - Java
【发布时间】:2011-05-12 22:30:58
【问题描述】:

全部,

我用 Java 编写了一个基于命令行的电话簿应用程序。该应用程序基本上会询问用户的一些详细信息,例如姓名、年龄、地址和电话号码,并将它们存储在一个文件中。其他操作包括通过姓名、电话号码等查找电话簿。所有详细信息都通过控制台输入。

我正在尝试为我已实现的每个功能编写 JUnit 测试用例,但无法弄清楚如何将实现代码中的 System.in 重定向到我的 JUnit 测试方法中的某些内容,当我的实际代码停止用户输入?

例子:

我的实现代码有:

BufferedReader is = new BufferedReader (new InputStreamReader(System.in));
System.out.println("Please enter your name:");
String name = is.readLine();             // My test cases stop at this line. How can I pass command line values i.e. redirect System.in to my test based values?

希望有意义

【问题讨论】:

标签: java command-line junit


【解决方案1】:

为什么不编写您的应用程序以将Reader 作为输入?这样,您可以轻松地将 InputStreamReader(System.in) 替换为 FileReader(testFile)

public class Processor {
    void processInput(Reader r){ ... }
}

然后是两个实例:

Processor live = new Processor(new InputStreamReader(System.in));
Processor test = new Processor(new FileReader("C:/tmp/tests.txt");

习惯于对界面进行编码将为您的程序的几乎所有方面带来巨大的好处!

还要注意Reader在Java 程序中处理基于字符的输入的惯用方式。 InputStreams 应保留用于原始字节级处理。

【讨论】:

  • 我不明白你的意思。上面发布的代码在我的示例中如何工作?因此,如果我必须在我的测试方法中传递命令行值,我必须使用文件 tests.txt 吗?如果是这种情况,我希望通过我的测试方法传递值,而不是创建文件来传递值。
  • 两个Reader 的接口相同。您使用FileReader 进行单元测试,因为它不需要用户输入(您准备文本文件以测试不同的输入)。您在实时执行程序时使用InputStreamReader(System.in),因此它仍会直接要求用户输入。
【解决方案2】:

【讨论】:

  • 你能详细说明你的答案吗?为什么我要使用文件“input.txt”??
  • @darkie15:这只是一个例子。
  • @darkie15:您也可以使用PipedInputStreamPipedOutputStream 左右。
【解决方案3】:

我建议你把代码分成三部分:

  • 读取输入(例如您的示例中的name
  • 使用该输入做您需要做的事情
  • 打印结果

您不需要测试读取输入和打印结果,因为这是编写 Java 的人已经测试过的 Java 代码。

您唯一需要测试的是正在做的事情,无论是什么。单元测试之所以这样命名,是因为它们单独测试代码单元。您不测试整个程序,您测试的是独立且具有明确功能的小块。

在单元测试中,您不应依赖输入/输出操作。您应该直接在单元测试中提供输入和预期输出。有时使用文件读取操作来提供输入或输出很方便(例如,如果数据量很大),但作为一般规则,您在单元测试中输入/输出越多,它们变得越复杂,您更有可能不做单元测试,而是集成测试。

在您的情况下,您以某种方式使用name。如果这是唯一的参数,那么创建一个方法——让我们称之为nameConsumer——采用该名称,执行某些操作并返回其结果。在你的单元测试中,做这样的事情:

@Test
public void testNameConsumer() {
    // Prepare inputs
    String name = "Jon";
    String result = nameConsumer(name);
    assertEquals("Doe", result);
}

将您的printlnreadLine 调用移至其他方法并在nameConsumer 周围使用,但不要在您的单元测试中使用。

在此处阅读有关此内容的更多信息:

保持简单,它有回报。

【讨论】:

    【解决方案4】:

    这需要 basic looping console application 并使其可测试,使用来自 oxbow_lakes' answer 的想法。

    类属性:

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    
    public class TestableLoopingConsoleExample {
    
       public static final String INPUT_LINE_PREFIX = "> ";
       public static final String EXIT_COMMAND = "exit";
       public static final String RESPONSE_PLACEHOLDER = "...response goes here...";
       public static final String EXIT_RESPONSE = "Exiting.";
    
       public static void main(String[] cmdLineParams_ignored) throws IOException {
          BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
          PrintStream out = new PrintStream(System.out);
          PrintStream err = new PrintStream(System.err);
    
          try {
             new TestableLoopingConsoleExample().main(cmdLineParams_ignored, in, out);
          } catch (Exception e) {  //For real use, catch only the exactly expected types
             err.println(e.toString());
          }
       }
    

    ...继续...

       public void main(String[] cmdLineParams_ignored, BufferedReader in, PrintStream out)
             throws IOException {
    
          System.out.println("Enter some text, or '" + EXIT_COMMAND + "' to quit");
    
          while (true) {
    
             out.print(INPUT_LINE_PREFIX);
             String input = in.readLine();
             out.println(input);
    
             if (input.length() == EXIT_COMMAND.length() &&
                input.toLowerCase().equals(EXIT_COMMAND)) {
    
                out.println(EXIT_RESPONSE);
                return;
             }
    
             out.println(RESPONSE_PLACEHOLDER);
          }
       }
    }
    

    测试(JUnit4):

    import static org.junit.Assert.assertEquals;
    import static testableloopingconsoleapp.TestableLoopingConsoleExample.EXIT_COMMAND;
    import static testableloopingconsoleapp.TestableLoopingConsoleExample.EXIT_RESPONSE;
    import static testableloopingconsoleapp.TestableLoopingConsoleExample.INPUT_LINE_PREFIX;
    import static testableloopingconsoleapp.TestableLoopingConsoleExample.RESPONSE_PLACEHOLDER; 
    
    import org.junit.Before;
    import org.junit.Test; 
    
    import java.io.BufferedReader;
    import java.io.ByteArrayOutputStream;
    import java.io.PrintStream;
    import java.io.StringReader; 
    
    public class TestableLoopingConsoleExampleTest { 
    
      private final ByteArrayOutputStream out = new ByteArrayOutputStream();
      private final ByteArrayOutputStream err = new ByteArrayOutputStream(); 
    
      @Before
      public final void resetOutputStreams() {
         out.reset();
         err.reset();
      } 
    

    ...继续...

      @Test
      public void testableMain_validInputFromString_outputAsExpected() throws Exception {
         String line1 = "input line 1\n";
         String line2 = "input line 2\n";
         String line3 = "input line 3\n";
         String exitLine = EXIT_COMMAND + "\n"; 
    
         BufferedReader in = new BufferedReader(new StringReader(
             line1 + line2 + line3 + exitLine
         ));
         String expectedOutput =
             INPUT_LINE_PREFIX + line1 +
             RESPONSE_PLACEHOLDER + "\n" +
             INPUT_LINE_PREFIX + line2 +
             RESPONSE_PLACEHOLDER + "\n" +
             INPUT_LINE_PREFIX + line3 +
             RESPONSE_PLACEHOLDER + "\n" +
             INPUT_LINE_PREFIX + exitLine +
             EXIT_RESPONSE + "\n"; 
    
         String[] ignoredCommandLineParams = null; 
    
         new TestableLoopingConsoleExample().main(ignoredCommandLineParams, in, new PrintStream(out)); 
    
         assertEquals(expectedOutput, out.toString());
      } 
    
    }
    

    【讨论】:

      【解决方案5】:

      System Rules 提供规则TextFromStandardInputStream 用于在JUnit 测试中模拟输入。

      public class YourAppTest {
        @Rule
        public TextFromStandardInputStream systemInMock = emptyStandardInputStream();
      
        @Test
        public void test() {
          systemInMock.provideText("name\nsomething else\n");
          YourApp.main();
          //assertSomething
        }
      }
      

      详情请看System Rules documentation

      此外,还有一个替代库 system-lambda,它利用了 Java 8 的可能性。适合替代 JUnit Jupiter 和 TestNG 中的“系统规则”。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-13
      • 1970-01-01
      • 2019-08-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多