关于java:如何测试命令行界面(CLI)?

How to test a Command Line Interface (CLI)?

我的Java应用程序由两部分组成:

  • 核心库(类,接口等)
  • 命令行界面(CLI),它使用核心库
  • 对于1.我使用JUnit进行单元测试,但是你会为2做什么?

    如何为命令行界面创建自动化测试?


    我有完全相同的问题,落在这里并没有找到一个好的答案,所以我想我会发布解决方案,我最终将作为未来降落在这里的任何人的起点。

    我在CLI之后写了我的测试(对我来说很遗憾,我知道),所以首先我确保CLI是以可测试的方式编写的。它看起来像这样(我省略了异常处理并简化了许多以使其更具可读性):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public class CLI {

        public static void main(String... args) {
            new CLI(args).startInterface();
        }

        CLI(String... args) {
            System.out.println("Welcome to the CLI!");
            // parse args, load resources, etc
        }

        void startInterface() {
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                String[] input = sanitiseInput(consoleReader.readLine());

                if (input[0].equalsIgnoreCase("help") {
                    help();
                } else if (input[0].equalsIgnoreCase("exit") {
                    break;
                } else if (input[0].equalsIgnoreCase("save") {
                    save(input);
                } else {
                    System.out.println("Unkown command.");
                }
            }
        }

        String[] sanitiseInput(String rawInput) {
            // process the input and return each part of it in order in an array, something like:
            return rawInput.trim().split("[ \t]+");
        }

        void help() {
            // print help information
            System.out.println("Helpful help.");
        }

        void save(String[] args) {
            // save something based on the argument(s)
        }
    }

    接受测试。 CLI不是公共库的一部分,因此应该保护它不受库用户的影响。如此处所述,您可以使用默认访问修饰符使其包为私有。这使得您的测试可以完全访问该类(只要它们位于同一个包中),同时仍然可以保护它,因此需要处理。

    为CLI接受的每个命令编写方法允许JUnit测试几乎完美地模拟用户输入。由于在调用startInterface()之前对象不会从stdin读取,因此您可以简单地实例化它并测试各个方法。

    首先,测试原始输入是否正确清理是很好的,您可以通过编写sanitiseInput()的JUnit测试来轻松完成。我写了这样的测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void commandAndArgumentsSeparatedBySpaces() throws Exception {
        String[] processedInput = uut.sanitiseInput("command argument1 argument2");

        assertEquals("Wrong array length.", 3, processedInput.length);
        assertEquals("command", processedInput[0]);
        assertEquals("argument1", processedInput[1]);
        assertEquals("argument2", processedInput[2]);
    }

    它也很容易覆盖一些边缘情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void leadingTrailingAndIntermediaryWhiteSpace() throws Exception {
        String[] processedInput = uut.sanitiseInput("  \t  this   \twas \t  \t  a triumph  \t\t   ");

        assertEquals("Wrong array length.", 4, processedInput.length);
        assertEquals("this", processedInput[0]);
        assertEquals("was", processedInput[1]);
        assertEquals("a", processedInput[2]);
        assertEquals("triumph", processedInput[3]);
    }

    接下来,我们可以通过监视stdout来测试invididual命令方法。我这样做了(我在这里找到):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private CLI uut;
    private ByteArrayOutputStream testOutput;
    private PrintStream console = System.out;
    private static final String EOL = System.getProperty("line.separator");

    @Before
    public void setUp() throws Exception {
        uut = new CLI();
        testOutput = new ByteArrayOutputStream();
    }

    @Test
    public void helpIsPrintedToStdout() throws Exception {
        try {
            System.setOut(new PrintStream(testOutput));
            uut.help();
        } finally {
            System.setOut(console);
        }
        assertEquals("Helpful help." + EOL, testOutput.toString());
    }

    换句话说,在练习之前用JVM的out代替你可以查询的内容,然后在测试的拆解中重新设置旧的控制台。

    当然,CLI应用程序通常不仅仅是打印到控制台。假设您的程序将信息保存到文件中,您可以像这样测试它(从JUnit 4.7开始):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    @Test
    public void informationIsSavedToFile() throws Exception {
        File testFile = tempFolder.newFile();
        String expectedContent ="This should be written to the file.";

        uut.save(testFile.getAbsolutePath(), expectedContent);

        try (Scanner scanner = new Scanner(testFile)) {
            String actualContent = scanner.useDelimiter("\\Z").next();
            assertEquals(actualContent, expectedContent);
        }
    }

    JUnit将负责创建一个有效的文件并在测试运行结束时将其删除,让您可以自由地测试CLI方法是否正确处理它。


    对于CLI,请尝试BATS(Bash自动测试系统):https://github.com/bats-core/bats-core

    来自文档:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    example.bats contains
    #!/usr/bin/env bats

    @test"addition using bc" {
      result="$(echo 2+2 | bc)"
      ["$result" -eq 4 ]
    }  

    @test"addition using dc" {
      result="$(echo 2 2+p | dc)"
      ["$result" -eq 4 ]
    }


    $ bats example.bats

     ? addition using bc
     ? addition using dc

    2 tests, 0 failures

    蝙蝠核


    使用Apache commons exec从Java程序执行命令。

    Apache Commons exec