Testing console based applications/programs - Java

前端 未结 5 1447
故里飘歌
故里飘歌 2020-12-03 05:47

All,

I have written a PhoneBook application in Java that is command line based. The application basically asks for some details of user like Name, Age, Address and p

相关标签:
5条回答
  • 2020-12-03 06:01

    I suggest you to separate the code into three parts:

    • Read input (like name in your example)
    • Do what you need to do with that input
    • Print the results

    You do not need to test reading input and printing results, as that's Java code that is already tested by people writing Java.

    The only thing you need to test is the thing you are doing, whatever that is. Unit tests are named like that because they tests units of code in isolation. You don't test the whole program, you test small pieces that are self-contained and have a well-defined function.

    In unit tests, you should not rely on input/output operations. You should provide inputs and expected outputs directly in the unit test. It is sometimes convenient to use File reading operations to supply input or output (e.g. if the amount of data is huge), but as a general rule the more you go into input/output in your unit tests, the more complex they become and you are more likely not to do unit, but integration tests.

    In your case, you use name somehow. If that is the only parameter, then make a method - let's call it nameConsumer - that takes that name, does something and returns its result. In your unit tests, do something like this:

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

    Move your println and readLine calls to other methods and use around nameConsumer, but not in your unit tests.

    Read more about this here:

    • http://haacked.com/archive/2008/07/22/unit-test-boundaries.aspx
    • C# example, but still: http://dotnet.dzone.com/news/unit-testing-file-io

    Keep it simple, it pays off.

    0 讨论(0)
  • 2020-12-03 06:14

    System.setIn(new BufferedInputStream(new FileInputStream("input.txt")));

    0 讨论(0)
  • 2020-12-03 06:17

    This takes a basic looping console application and makes it testable, using the ideas from oxbow_lakes' answer.

    The class-proper:

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

    ...continued...

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

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

    ...continued...

      @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());
      } 
    
    }
    
    0 讨论(0)
  • 2020-12-03 06:22

    Why not write your application to take a Reader as input? That way, you can easily replace an InputStreamReader(System.in) with a FileReader(testFile)

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

    And then two instances:

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

    Getting used to coding to an interface will bring great benefits in almost every aspect of your programs!

    Note also that a Reader is the idiomatic way to process character-based input in Java programs. InputStreams should be reserved for raw byte-level processing.

    0 讨论(0)
  • 2020-12-03 06:24

    The library System Rules provides the rule TextFromStandardInputStream for simulating input in JUnit tests.

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

    For details have a look at the System Rules documentation.

    0 讨论(0)
提交回复
热议问题