Having an interface similar to unix command line tools is not a trivial task but with args4j it becomes quite easy.
We're going to write a fake tool called FileCompressor which process of invocation would look like:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
compressor -i inputFile.txt -o outputFile.txt -p high |
Let's start with defining the dependencies of our project:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>args4j</groupId> | |
<artifactId>args4j</artifactId> | |
<version>2.0.23</version> | |
</dependency> | |
<dependency> | |
<groupId>junit</groupId> | |
<artifactId>junit</artifactId> | |
<version>4.11</version> | |
<scope>test</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-test</artifactId> | |
<version>3.2.2.RELEASE</version> | |
<scope>test</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.mockito</groupId> | |
<artifactId>mockito-core</artifactId> | |
<version>1.9.5</version> | |
<scope>test</scope> | |
</dependency> |
Our fake FileCompressor requires a configuration that will be populated by args4j.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FileCompressor { | |
public void compressFile(Configuration configuration) { | |
System.out.println("Received: " + configuration); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Configuration { | |
private enum Priority { | |
HIGH, LOW; | |
} | |
@Option(name = "-i", usage = "input file name", required = true) | |
private String inputFileName; | |
@Option(name = "-o", usage = "output file name", required = true) | |
private String outputFileName; | |
@Option(name = "-p", usage = "process priority") | |
private Priority priority = Priority.HIGH; | |
public String getInputFileName() { | |
return inputFileName; | |
} | |
public String getOutputFileName() { | |
return outputFileName; | |
} | |
public Priority getPriority() { | |
return priority; | |
} | |
@Override | |
public String toString() { | |
return "Configuration{" + | |
"inputFileName='" + inputFileName + '\'' + | |
", outputFileName='" + outputFileName + '\'' + | |
", priority=" + priority + | |
'}'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class App { | |
private static Configuration configuration = new Configuration(); | |
private static FileCompressor fileCompressor = new FileCompressor(); | |
private static CmdLineParser parser = new CmdLineParser(configuration); | |
private static PrintStream outputStream = System.out; | |
public static void main(String[] args) { | |
try { | |
parser.parseArgument(args); | |
fileCompressor.compressFile(configuration); | |
} catch (CmdLineException e) { | |
outputStream.println(e.getMessage()); | |
parser.printUsage(outputStream); | |
} | |
} | |
} |
If there is a parsing problem, the exception is printed along with the usage (args4j can automatically print the usage of our application based on used annotations).
Testing our application requires some effort.
To begin with, we would like to mock completely the FileCompressor. There is no setter for it so we'll use Spring's ReflectionTestUtils together with Mockito.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@RunWith(MockitoJUnitRunner.class) | |
public class AppTest { | |
private static final String INPUT_FILE = "input.txt"; | |
private static final String OUTPUT_FILE = "output.txt"; | |
private static final String INVALID_ARGUMENT = "-www"; | |
@Mock | |
private FileCompressor fileCompressor; | |
private App app = new App(); | |
@Before | |
public void setUp() { | |
setField(app, "fileCompressor", fileCompressor); | |
} |
Let's check if the configuration object is populated properly and if the collabolator was invoked:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
public void shouldPopulateConfiguration() { | |
// when | |
app.main((String[]) asList("-i", INPUT_FILE, "-o", OUTPUT_FILE).toArray()); | |
// then | |
Configuration configuration = (Configuration) getField(app, "configuration"); | |
assertThat(configuration.getInputFileName(), is(INPUT_FILE)); | |
assertThat(configuration.getOutputFileName(), is(OUTPUT_FILE)); | |
} | |
@Test | |
public void shouldInvokeFileCompressor() { | |
// when | |
app.main((String[]) asList("-i", INPUT_FILE, "-o", OUTPUT_FILE).toArray()); | |
// then | |
verify(fileCompressor).compressFile(isA(Configuration.class)); | |
} |
To do that we need to use a spy, as we want to have the original behavior of populating argument, but we want to verify if printUsage method was invoked.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
public void shouldPrintUsageWhenInvalidArguments() { | |
// given | |
Configuration configuration = (Configuration) getField(app, "configuration"); | |
CmdLineParser parser = spy(new CmdLineParser(configuration)); | |
setField(app, "parser", parser); | |
String[] invalidArguments = (String[]) asList(INVALID_ARGUMENT, INPUT_FILE).toArray(); | |
// when | |
app.main(invalidArguments); | |
// then | |
verify(parser).printUsage(isA(PrintStream.class)); | |
} |
Similarly we need to use a spy.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
public void shouldPrintExceptionOnOutputStreamWhenInvalidArguments() { | |
// given | |
PrintStream outputStream = spy(System.out); | |
setField(app, "outputStream", outputStream); | |
String[] invalidArguments = (String[]) asList(INVALID_ARGUMENT, INPUT_FILE).toArray(); | |
// when | |
app.main(invalidArguments); | |
// then | |
verify(outputStream).println("\"" + INVALID_ARGUMENT + "\" is not a valid option"); | |
} |