How to test a Command Line Interface (CLI)? - java

My Java application consists of two parts:
core libraries (classes, interfaces, etc)
command line interface (CLI), which uses the core libraries
For 1. I use JUnit for unit testing, but what would you do for 2.?
How can I create automated tests for a command line interface?

I had the exact same problem, landed here and didn't find a good answer, so I thought I would post the solution I eventually came to as a starting point for anyone who lands here in the future.
I wrote my tests after the CLI (shame on me, I know), so first I made sure the CLI was written in a testable way. It looks something like this (I've omitted the exception handling and simplified a lot to make it more readable):
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)
}
}
On to testing. CLI is not a part of the public libraries, so it should be protected from library users. As is mentioned here, you can use the default access modifier to make it package private. This gives your tests full access to the class (as long as they are in the same package) while still protecting it, so that's that taken care of.
Writing a method for each command accepted by the CLI allows JUnit tests to almost perfectly simulate user input. Since the object won't read from stdin until you call startInterface(), you can simply instantiate it and test the individual methods.
First, it's good to test that the raw input is being correctly sanitised, which you can do trivially by writing JUnit tests for sanitiseInput(). I wrote tests like this:
#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]);
}
It's easy to cover some edge cases too:
#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]);
}
Next we can test the invididual command methods by monitoring stdout. I did this (which I found here):
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());
}
In other words, substitute the JVM's out with something you can query just before the exercise, and then set the old console back in the test's teardown.
Of course, CLI applications often do more than just print to the console. Supposing your program saves information to a file, you could test it as such (as of JUnit 4.7):
#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 will take care of creating a valid file and removing it at the end of the test run, leaving you free to test that it is properly treated by the CLI methods.

For any CLI you can use BATS (Bash Automated Testing System):
The test-specification from the docs is a script-file like example.bats:
#!/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 ]
}
When using the bats command to execute and the output look like this:
$ bats example.bats
✓ addition using bc
✓ addition using dc
2 tests, 0 failures
See related tag for more questions: bats-core

Related

How to make JUnit test for void method with Scanner and while loop [duplicate]

This question already has answers here:
JUnit test for System.out.println()
(14 answers)
Closed 4 years ago.
I want to test method setDay() using arguments [-1,0,24,2,32].
I know that Scanner reader can be
String test="-1 0 24 2 32";
Scanner reader=new Scanner(test);
The main problem is infinite loop and void method. How can we test this kind of code? Here is example code:
public NameOfTheDay(){
int day=1;
}
{...}
public void setDay(Scanner reader) {
while (true) {
System.out.print("Day: ");
String input = reader.nextLine();
if (input.matches("\\d{2}")) {
int day = Integer.parseInt(input);
if (day > 0 && day < 32) {
this.day = day;
return;
}
}
System.out.println("Wrong day. Try again.");
}
}
Thanks for the answer.
How can we test this kind of code?
You cannot.
unittest verify the public observable behavior of your code under test where "public observable behavior" is any rreturn value or communication with dependencies.
Communication with dependencies is checked with test doubles which we (usually) create using a mocking framework and which we inject into the code under test.
A major prerequest is that you code cleanly incorporates Single Responsibility/Separation of Concerns pattern.
Your code does not return anything and has no possibility to replace the dependencies of interest (here System.out) because it mixes business logic with user interaction.
Some may argue, that you can assign a test double to System.out or use PowerMock to replace dependencies but IMHO this is just a surrender to your bad design and will not pay off as your program grows.
I will not focus on the contents of your method, but just on the question on how to unit test a method expecting a Scanner object as parameter.
The easy answer is: Provide your test input data as a String, and build a scanner around it, like this:
#Test
public void testSetDay_positive() {
String testInput = "23\n";
Scanner testScanner = new Scanner(testInput);
NameOfTheDay notd = new NameOfTheDay();
notd.setDay(testScanner);
Assert.assertEquals(23, notd.getDay()); // or whatever condition to test
}
Now it gets harder. Perhaps you want to test an invalid input first, and make sure the second input is used then?
#Test
public void testSetDay_negative_then_positive() {
String testInput = "999\n23\n"; // two lines of input here
Scanner testScanner = new Scanner(testInput);
NameOfTheDay notd = new NameOfTheDay();
notd.setDay(testScanner);
Assert.assertEquals(23, notd.getDay()); // or whatever condition to test
}
If you want to test if the error message is written to System.out, you would have to replace that with a custom stream to test afterwards:
ByteArrayOutputStream mockOut = new ByteArrayOutputStream();
PrintStream newOut = new PrintStream(mockOut);
System.setOut(newOut);
// execute test from above
Assert.assertTrue(new String(mockOut.toByteArray(), "UTF-8").contains("Wrong day. Try again."));
Still, most comments to your question contain valuable input (move validation to an extra method etc.) which should be considered.

Configure execution to skip specific tests

I'm creating a test jar with a bunch of JUnit tests on it. Since I'm testing a 3rd part code, I don't run the tests with "mvn test", but I generate an executable jar and run it with "java -jar"
On my tests, I have many different categories, and I'd like to choose which one is executed, which isn't on the command line.
I tried the option -Dgroups="categories", but it does not work with "java -jar"
The thing I want to do is something like that:
java -jar -Dcategories="cat1,cat2,cat5" executable.jar
The only way I see about reading this on my code is with
System.getProperty("categories");
I tried using something like that:
#Before
public void setup() {
Assume.assumeTrue(System.getProperty("categories") != null && System.getProperty("categories").contains("cat1"));
}
It skips the test but still gives me a stack trace, which looks pretty bad.
Any other option to skip the tests?
The JAVA Documentation says:
java [ options ] -jar file.jar [ argument ... ]
so in your case
java -jar executable.jar cat1 cat2 cat5
public static void main(String[] args) {
for (String input : args) {
if (input.equalsIgnoreCase("cat1")) {
runTests(TestOne.class);
} else {
runTests(TestTwo.class);
}
}
}
private static void runTests(Class test) {
Result result = JUnitCore.runClasses(test);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
}

best practice to validate input java

I'm looking for suggestions on how to go about validating input from a user. My assignment is to execute commands based on a textual input from the user. My only concern is that there can be many variations of commands that are acceptable.
For example these commands are all acceptable and do the same thing, "show the game board"
sh board,
sho board,
show board,
show bo,
sho bo,
sh bo
There are about 10 other commands that share this similar property so I was wondering what would be the best practice of going about validating a users input?
Should I store all the different combinations in a hashmap?
Look into regex (regular expressions). These are great for when you want to use values that are not necessarily complete.
For example:
Say I type "shutdo"
With regex you can make your program understand that anything after the string "shutd" means to powerOff()
It looks like the minimum command allowed length is 2.
So first you check if the length of the term is at least 2.
Next, you can loop over the available commands,
and stop at the first that starts with the term, for example:
List<String> commands = Arrays.asList("show", "create", "delete");
for (String command : commands) {
if (command.startsWith(term)) {
// found a match, command is: command
break;
}
}
If the commands are very specific and limited, I would just add all of them into some data structure (hash being one of them).
If the problem was that you're supposed to understand what the user input is supposed to do, then I would say find the pattern using either regex or a simple pattern validation (looks like they're all two words, first starting with "sh" and second starting with "bo").
But honestly, ~15 commands aren't that big of deal in terms of space/efficiency.
Edit:
There are about 10 other commands that share this similar property
If this means 10 more commands like "show board", then I would say store it in hash. But if I misunderstood you and you mean that there are 10 other commands that do similar things ("set piece", "set pie", "se pi", etc), then RegEx is the way to go.
If I understood you correctly, there are N distinct commands, which can be combined. It shall be allowed to abbreviate each command as long it stays unambiguous.
If this is the case, the following methods expandCommands(String) and expandCommand(String) will normalize each command part.
public class Main {
static Set<String> availableCommands = new HashSet<>(Arrays.asList(
"show",
"board",
"btest"
));
public static void main(String[] args) throws Exception {
List<String> testData = Arrays.asList(
"sh board",
"sho board",
"show board",
"show bo",
"sho bo",
"sh bo"
);
String expected = "show board";
for (String test : testData) {
String actual = expandCommands(test);
if (!expected.equals(actual)) {
System.out.println(test + "\t"+ actual);
}
}
try {
expandCommands("sh b");
throw new IllegalStateException();
} catch (Exception e) {
if (!"not unique command: b".equals(e.getMessage())) {
throw new Exception();
}
}
try {
expandCommands("sh asd");
throw new IllegalStateException();
} catch (Exception e) {
if (!"unknown command: asd".equals(e.getMessage())) {
throw new Exception();
}
}
}
private static String expandCommands(String aInput) throws Exception {
final String[] commandParts = aInput.split("\\s+");
StringBuilder result = new StringBuilder();
for (String commandPart : commandParts) {
String command = expandCommand(commandPart);
result.append(command).append(" ");
}
return result.toString().trim();
}
private static String expandCommand(final String aCommandPart) throws Exception {
String match = null;
for (String candidate : availableCommands) {
if (candidate.startsWith(aCommandPart)) {
if (match != null) {
throw new Exception("not unique command: " + aCommandPart);
}
match = candidate;
}
}
if (match == null) {
throw new Exception("unknown command: " + aCommandPart);
}
return match;
}
}
The Set<String> availableCommands contains all possible commands.
Every part of the input command is checked, if it is the start of exactly one available command.
You can use reg-ex matching to validate input. E.g., the pattern below will match anything that starts with sh followed by 0 or more characters, then a space and then bo followed by 0 or more chars.
public class Validator {
public static void main (String[] args) {
String pattern = "sh[\\w]* bo[\\w]*";
System.out.println(args[0].matches(pattern));
}
}

Giving input for JUnit test

Hi this is the first time im trying out unit testing in java using eclipse.
So when i test my class, it requires user input. Lets say a command called "add hello", so that it will create a new textfile and add the word "hello" to it. Then i want to test a function called getAllLines which returns "hello" and compare it with assert.
My main problem is how to simulate user input to console via junit test. This is what i tried but its not working..
private void performUserInput(String strInput){
ByteArrayInputStream in = new ByteArrayInputStream(strInput.getBytes());
System.setIn(in);
}
private void releaseUserInputToSystem(){
System.setIn(System.in);;
}
#Test
public void testSearchingInEmptyFile() {
TextBuddy textBuddy = new TextBuddy("file.txt");
textBuddy.run();
performUserInput("add little brown fox");
releaseUserInputToSystem();
assertEquals("little brown foxx", "asd");
}
It seems to me like the code never reaches assert.
edit----------------------------------------------
After debugging, its getting stuck here
private String[] getCommandAndArgs(){
String[] splitCommand = scanner.nextLine().split(" "); //<<STUCK HERE
printNewLine();
return splitCommand;
}
With the Unit-Test you should rather test single Methods (units) of your TextBuddy Class. You probably have a method which check the commands (add, remove, whatever you have). Write Unit Tests for those e.g.:
#Test
public void testCommandAdd() {
TextBuddy tb = new TextBuddy ();
int command tb.parseCommand("add hello");
assertThat(command,is(TextBuddy.ADD));
}
#Test
public void testCommandRemove() {
TextBuddy tb = new TextBuddy ();
int command tb.parseCommand("remove hello");
assertThat(command,is(TextBuddy.REMOVE));
}
Then write tests for each command, e.g. that a file was written/deleted whatever:
#Test
public void testWriteFile() throws SQLException {
TextBuddy tb = new TextBuddy ();
tb.writeFile("file.txt", "hello");
File f = new File("file.txt");
String content = readFile(f);
assertThat(content,is("hello"));
}
Always test single units of your program with small Unit Tests. Later you can write bigger Tests that check if your hole Program works.
If you don't want to expose your Methods with the public modifier, you can still test them - the simplest way is makeing them package-private and have your tests the same package (they can and should be in a differnet src-folder)
e.g. for a Class with the package com.yourpackage like this
src/com/yourpackage/YourClass.java
You could store your test in
test/com/yourpackage/YourClassTest.java
Then you can access package-private Methods.
Or you use Reflection to access and test a private Method, see here and here
A unit test is all about automation and should not rely on user input. If you want to test a user input mechanism, you should write a test that simulates a user entering the input to be tested (e.g. using Selenium for front end testing a web application). If you want to test behavior based on input, you should contruct tests that enter all possibilities that shall be tested automatically and validate the respective correct behavior of your application/program/functionality.
If TextBuddy.run() is specified to read something from System.in, it looks like a good idea to redirect System.in before the call to run():
performUserInput("add little brown fox");
textBuddy.run();
releaseUserInputToSystem();
But maybe you could improve your TextBuddy to be more testable by adding the input stream to read from as a parameter to the run method:
public void run(InputStream in) {
// use parameter in instead of System in...
}
/**
* Convenience method to run with System.in
*/
public void run() {
run(System.in);
}
In your test, call run(InputStream):
ByteArrayInputStream in = new ByteArrayInputStream(strInput.getBytes());
textBuddy.run(in);
You could also add an out parameter to be able to redirect the output of your tested method.
I agree with #LarsGendner you should mock (do some simulate or fake things) for the input task. You can use some technique that provides input to coverage at least for some part of this code (for example TextBddy). In this case, I can suggest three approaches.
static inputs use this approach if you need to have some text samples derived from empirical testing. You can use this form model some typical behavior from existing users;
random string generator use this approach to do string generation from scratch (you can combine this approach with static input)
framework (or library) to extend jUnit capability in towards of data mining or artificial intelligence such as genetics algorithms, neural networks and so on.
In my onion the last technique is more sophisticate. So, you should evaluate and perform a thread-off to decide which parts should be testing and which should be the code coverage that you need. Notice, that mocking some input task according to human behavior it is not trivial task.
Regarding your original code, when you execute textBuddy.run(); it will lock expecting user input, so the next line (performUserInput("add little brown fox"); which does provide the input it is waiting for) is never executed.
So, to make that code work, you should call performUserInput("add little brown fox"); before calling textBuddy.run():
#Test
public void testSearchingInEmptyFile() {
TextBuddy textBuddy = new TextBuddy("file.txt");
performUserInput("add little brown fox"); // changed these two lines
textBuddy.run(); // switched their order
releaseUserInputToSystem();
assertEquals("little brown foxx", "asd");
}
Another approach is to use threads: execute textBuddy.run() in one thread and performUserInput("add little brown fox") in other. It is probably best to avoid this, though, as threads may make your tests way more difficult to maintain.
Full demo code:
public class TextBuddyTest {
private InputStream performUserInput(String strInput) {
InputStream originalSystemIn = System.in;
ByteArrayInputStream in = new ByteArrayInputStream(strInput.getBytes());
System.setIn(in);
return originalSystemIn;
}
private void restoreSystemInputStream(InputStream originalSystemIn) {
System.setIn(originalSystemIn);
}
#Test
public void testSearchingInEmptyFile() {
// setup
InputStream defaultSystemIn = performUserInput("add little brown fox");
TextBuddy textBuddy = new TextBuddy("file.txt");
// execute
textBuddy.run();
// verify
assertEquals("[add, little, brown, fox]", textBuddy.getWhatWasInput());
// teardown
restoreSystemInputStream(defaultSystemIn);
}
static class TextBuddy { // created for this demo
Scanner scanner = new Scanner(System.in);
String whatWasInput;
public TextBuddy(String s) { }
public void run() { this.whatWasInput = Arrays.toString(getCommandAndArgs()); }
private String[] getCommandAndArgs() { return scanner.nextLine().split(" "); }
public String getWhatWasInput() { return whatWasInput; }
}
}

How to test a print method in Java using Junit [duplicate]

This question already has answers here:
JUnit test for System.out.println()
(14 answers)
Closed 6 years ago.
I have written a method that is printing output to a console. How should I test it?
public class PrinterForConsole implements Printer<Item>{
public void printResult(List<Item> items) {
for (Item item: items){
System.out.println("Name: " + item.getName());
System.out.println("Number: " + item.getNumber());
}
}
}
currently, my test looks like this
public class TestPrinter{
#Test
public void printResultTest() throws Exception {
(am figuring out what to put here)
}
}
I have read the solution at this post (thanks #Codebender and #KDM for highlighting this) but don't quite understand it. How does the solution there test the print(List items) method? Hence, asking it afresh here.
Since you have put you don't get what the duplicate question says, I will try to explain a little.
When you do, System.setOut(OutputStream), whatever the application writes to the console (using System.out.printX()) statements, instead get written to the outputStream you pass.
So, you can do something like,
public void printTest() throws Exception {
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));
// After this all System.out.println() statements will come to outContent stream.
// So, you can normally call,
print(items); // I will assume items is already initialized properly.
//Now you have to validate the output. Let's say items had 1 element.
// With name as FirstElement and number as 1.
String expectedOutput = "Name: FirstElement\nNumber: 1" // Notice the \n for new line.
// Do the actual assertion.
assertEquals(expectedOutput, outContent.toString());
}
The best way to test it is by refactoring it to accept a PrintStream as a parameter and you can pass another PrintStream constructed out of ByteArrayOutputStream and check what is printed into the baos.
Otherwise, you can use System.setOut to set your standard output to another stream. You can verify what is written into it after the method returns.
A simplified version with comments is below:
#Test
public void printTest() throws Exception {
// Create our test list of items
ArrayList<Item> items = new ArrayList<Item>();
items.add(new Item("KDM", 1810));
items.add(new Item("Roy", 2010));
// Keep current System.out with us
PrintStream oldOut = System.out;
// Create a ByteArrayOutputStream so that we can get the output
// from the call to print
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Change System.out to point out to our stream
System.setOut(new PrintStream(baos));
print(items);
// Reset the System.out
System.setOut(oldOut);
// Our baos has the content from the print statement
String output = new String(baos.toByteArray());
// Add some assertions out output
assertTrue(output.contains("Name: KDM"));
assertTrue(output.contains("Name: Roy"));
System.out.println(output);
}
Note that if the print method throws an exception, the System.out is not reset. It is better to use setup and teardown methods to set and reset this.
How about something like this.
#Test
public void printTest() throws Exception {
OutputStream os = new ByteArrayOutputStream();
System.setOut(os);
objectInTest.print(items);
String actualOutput = os.toString("UTF-8");
assertEquals(expectedOutput, actualOutput);
}
This is the Simple Test Code :-
#Test
public void out() {
System.out.print("hello");
assertEquals("helloworld", outContent.toString());
}
#Test
public void err() {
System.err.print("helloworld 1 ");
assertEquals("helloworld 1", errContent.toString());
}
For more :JUnit test for System.out.println()
Eventually, what I came up with is this (after going through all the answers and links to possible duplicates above).
import org.junit.Test;
#Test
public void shouldPrintToConsole() throws Exception {
Item testItem = new Item("Name", "Number");
List<Item> items = Arrays.asList(testItem);
Printer print = new Printer();
printer.printOutput(items);
}
Read up on naming convention (shouldPrintToConsole()) for testing too. Wondering if this is the convention because I see many sites that follow and many that don't.

Categories

Resources