remkop / picocli

Picocli is a modern framework for building powerful, user-friendly, GraalVM-enabled command line apps with ease. It supports colors, autocompletion, subcommands, and more. In 1 source file so apps can include as source & avoid adding a dependency. Written in Java, usable from Groovy, Kotlin, Scala, etc.
https://picocli.info
Apache License 2.0
4.94k stars 424 forks source link

Create simple test framework for testing JVM and native CLI apps #894

Open remkop opened 4 years ago

remkop commented 4 years ago

Create simple test framework for testing JVM and native CLI apps.

Ideally allows the same test to be applied to a CommandLine object executed in the JVM, as well as to a native image executed as a separate Process.

remkop commented 4 years ago

@maxandersen tweeted about these interesting alternatives:

maxandersen commented 4 years ago

there is https://github.com/intuit/karate/issues/1191 too - but i think some small java utility to just test cli's and assert on stdout/err as strings and potentially streams too would still be powerful..

sebhoss commented 4 years ago

https://github.com/stefanbirkner/system-lambda might be an option as well

rnc commented 4 years ago

I am using system-lambda in this PR (not yet merged) : https://github.com/project-ncl/bacon/pull/431/files#diff-006308d763b4c36013a8c98e99b01992R63 so you can do

        String text = tapSystemErr(
                () -> assertEquals(
                        1,
                        new App().run(
                                new String[] { "-o", "-p", configYaml.toString(), "-v", "pnc", "build", "get", "0" })));
        assertTrue(text.contains("JSON command is enabled: true"));
ebramirez commented 4 years ago

Hi @remkop,

I don't know if you already have an idea of how this would be modeled. If not, I have a suggestion:

What we have done to test our CLI application in JVM and Native modes is to create a set of classes:

Here is a pseudo code example:

If you have a command class:

@Command()
public class MyCommand {
    @Option
    private Integer one;

    @Option
    private String two;
}

PicoCLI would generate this classes:

public abstract class MyCommandLauncher {
    public static MyCommandLauncher create() {
        return native ? new MyCommandLauncherNative() : new MyCommandLauncherJVM();
    }

    public abstract List<String> getCommand();

    public int run( MyCommandParameters parameters ) {
        Process process = new ProcessBuilder( getCommand().addAll( parameters.getParameters() ).start();

        process.waitFor();

        return process.exitValue();
    }
}
protected class MyCommandLauncherJVM extends MyCommandLauncher {
    public List<String> getCommand() {
        // return the command line to launch the jar file.
    }
}
protected class MyCommandLauncherNative extends MyCommandLauncher {
    public List<String> getCommand() {
        // return the command line to launch the native executable.
    }
}
// This class could use a constructor or a builder pattern
public class MyCommandParameters {
    private Integer one;
    private String two;

    //  Getters and Setters

    public List<String> getParameters() {
        // return the parameters for the cli
    }
}

And they could be used in a test:

class MyCommandTest {
    MyCommandLauncher myApp = MyCommandLauncher.create();

    @Test 
    void myTest() {
        MyCommandParameters params = MyCommandParameters.builder().one( 1 ).two ( "two" ).build();

        int exitValue = myApp.run( params );

        assertThat( exitValue ).isEqualTo( 0 );
    }
}
remkop commented 4 years ago

@ebramirez I did some initial prototyping here. Example usage here. I haven't spent much time on this though.