Much testing

This commit is contained in:
Paul Schaub 2018-11-15 01:30:41 +01:00
parent 30aeef5da5
commit fa3b665047
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 335 additions and 27 deletions

View file

@ -3,7 +3,7 @@ package de.vanitasvitae.imi.codes;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
public class Arguments { class Arguments {
static final Option STUDY_NUMBER = Option.builder("s") static final Option STUDY_NUMBER = Option.builder("s")
.longOpt("study") .longOpt("study")
@ -16,20 +16,28 @@ public class Arguments {
static final Option SAMPLE_TYPE = Option.builder("t") static final Option SAMPLE_TYPE = Option.builder("t")
.longOpt("type") .longOpt("type")
.desc("Type of the sample (b = blood, u = urine)") .desc("Type of the sample (b = blood, u = urine)")
.required(true) .required()
.hasArg() .hasArg()
.argName("sample type") .argName("sample type")
.build(); .build();
static final Option NUMBER_CODES = Option.builder("n")
.longOpt("number")
.desc("Number of codes to be generated")
.required()
.hasArg()
.argName("n")
.build();
static final Option OUTPUT_DESTINATION = Option.builder("o") static final Option OUTPUT_DESTINATION = Option.builder("o")
.longOpt("OUTPUT_DESTINATION") .longOpt("output")
.desc("Filename for the generated html output") .desc("Filename for the generated html output")
.hasArg() .hasArg()
.argName("file name") .argName("file name")
.build(); .build();
static final Option EXTERNAL_BROWSER = Option.builder("b") static final Option EXTERNAL_BROWSER = Option.builder("b")
.longOpt("EXTERNAL_BROWSER") .longOpt("browser")
.desc("Open the generated HTML output in a an external browser") .desc("Open the generated HTML output in a an external browser")
.build(); .build();
@ -43,14 +51,12 @@ public class Arguments {
* @return command line options. * @return command line options.
*/ */
public static Options getCommandLineOptions() { public static Options getCommandLineOptions() {
Options options = new Options(); return new Options()
.addOption(STUDY_NUMBER)
options.addOption(STUDY_NUMBER); .addOption(SAMPLE_TYPE)
options.addOption(SAMPLE_TYPE); .addOption(NUMBER_CODES)
options.addOption(OUTPUT_DESTINATION); .addOption(OUTPUT_DESTINATION)
options.addOption(EXTERNAL_BROWSER); .addOption(EXTERNAL_BROWSER)
options.addOption(HELP); .addOption(HELP);
return options;
} }
} }

View file

@ -5,7 +5,7 @@ import java.util.regex.Pattern;
public class InputValidator { public class InputValidator {
// Allowed characters // Allowed characters
private static final String ALPHABET = "[a-zA-Z0-9]"; public static final String ALPHABET = "[a-zA-Z0-9]";
/* /*
REGEX pattern for study numbers. Allowed are all 3-letter numbers from the alphabet [a-zA-Z0-9]. REGEX pattern for study numbers. Allowed are all 3-letter numbers from the alphabet [a-zA-Z0-9].
@ -22,11 +22,12 @@ public class InputValidator {
* *
* @param in input String * @param in input String
* @return unmodified input String if it matches. * @return unmodified input String if it matches.
* @throws IllegalArgumentException if the input String doesn't match the regex. *
* @throws InvalidOptionException if the input String doesn't match the regex.
*/ */
public static String validateStudyNumber(String in) { public static String validateStudyNumber(String in) throws InvalidOptionException {
if (!STUDY_NUMBER_MATCHER.matcher(in).matches()) { if (!STUDY_NUMBER_MATCHER.matcher(in).matches()) {
throw new IllegalArgumentException("Study number does not match REGEX."); throw new InvalidOptionException("Study number must be a three digit number from the alphabet [a-zA-Z0-9].");
} }
return in; return in;
@ -38,10 +39,38 @@ public class InputValidator {
* @param in input String * @param in input String
* @return parsed {@link SampleType}. * @return parsed {@link SampleType}.
* *
* @throws IllegalArgumentException if the given String doesn't match a {@link SampleType}. * @throws InvalidOptionException if the given String doesn't match a {@link SampleType}.
* @throws NullPointerException in case the given String is {@code null}. * @throws NullPointerException in case the given String is {@code null}.
*/ */
public static SampleType validateSampleType(String in) { public static SampleType validateSampleType(String in) throws InvalidOptionException {
return SampleType.valueOf(in); try {
return SampleType.getEnum(in);
} catch (IllegalArgumentException e) {
String message = "Invalid sample type \"" + in + "\".";
throw new InvalidOptionException(message, e);
}
}
/**
* Validate, that the user provided number of codes to be generated is an integer greater than 0.
*
* @param in input String
* @return number of codes to be generated.
*
* @throws InvalidOptionException in case the input string is
*/
public static int validateNumberOfCodes(String in) throws InvalidOptionException {
int i;
try {
i = Integer.parseInt(in);
} catch (NumberFormatException e) {
throw new InvalidOptionException("Invalid input \"" + in + "\". Please enter a positive number.", e);
}
if (i <= 0) {
throw new InvalidOptionException("Number of generated codes must be greater than 0.");
}
return i;
} }
} }

View file

@ -0,0 +1,28 @@
package de.vanitasvitae.imi.codes;
/**
* Exception which gets thrown when the user-provided input arguments are not valid.
*/
public class InvalidOptionException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Create a new {@link InvalidOptionException}. The message MUST be human readable, as it will get printed to the
* CLI.
* @param message human readable error message.
*/
public InvalidOptionException(String message) {
super(message);
}
/**
* Create a new {@link InvalidOptionException}. The message MUST be human readable, as it will get printed to the
* CLI.
* @param message human readable error message.
* @param cause cause of the exception.
*/
public InvalidOptionException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -14,7 +14,6 @@ public class Main {
private static final String HELP_FOOTER = "\nAuthor: Paul Schaub <paul.schaub@wwu.de>"; private static final String HELP_FOOTER = "\nAuthor: Paul Schaub <paul.schaub@wwu.de>";
public static void main(String[] args) { public static void main(String[] args) {
Options options = Arguments.getCommandLineOptions(); Options options = Arguments.getCommandLineOptions();
CommandLineParser parser = new DefaultParser(); CommandLineParser parser = new DefaultParser();
@ -27,6 +26,7 @@ public class Main {
return; return;
} }
// User issues '-h', so just show help text and exit.
if (arguments.hasOption(Arguments.HELP)) { if (arguments.hasOption(Arguments.HELP)) {
printHelp(options); printHelp(options);
return; return;

View file

@ -2,19 +2,61 @@ package de.vanitasvitae.imi.codes;
public enum SampleType { public enum SampleType {
b("Blood"), // Hard coded values. Prepended with an underscore to allow numeric values (eg. _2).
u("Urine"), _b('b', "Blood"),
_u('u', "Urine"),
; ;
// Members of the enum // Members of the enum
private final String name; private final char name;
private final String description;
SampleType(String name) { /**
* Create a SampleType enum which is described by a name (one letter from the alphabet [a-zA-Z0-9]) and a
* human readable description.
*
* @param name one letter name
* @param description description
*/
SampleType(char name, String description) {
this.name = name; this.name = name;
this.description = description;
} }
public String getName() { /**
return name; * Override {@link Enum#toString()} in order to return the name of the enum instead of its value.
*
* @return name
*/
@Override
public String toString() {
return "" + name;
}
/**
* Return the human readable description of the enum.
*/
public String getDescription() {
return description;
}
/**
* Replacement method for {@link #valueOf(String)}. You MUST use this method instead, as we have to escape
* the names of the enums with a underscore in order to allow for numerals to be used as enum name (for example "_0").
* This method takes a string value (eg. "u") and returns the corresponding enum ("_u").
*
* @param name name of the enum without an underscore.
* @return enum corresponding to the name.
*
* @throws IllegalArgumentException if no matching enum was found.
*/
public static SampleType getEnum(String name) {
for (SampleType s : SampleType.values()) {
if (s.toString().equals(name)) {
return s;
}
}
throw new IllegalArgumentException("No SampleType found for name \"" + name + "\".");
} }
} }

View file

@ -0,0 +1,164 @@
package de.vanitasvitae.imi.codes;
import static junit.framework.TestCase.assertEquals;
import java.util.Random;
import org.junit.Test;
public class InputValidatorTest {
private static final Random RAND = new Random();
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/**
* Test, whether {@link InputValidator#validateStudyNumber(String)} accepts 3 digit inputs from the allowed alphabet.
*
* @throws InvalidOptionException NOT expected.
*/
@Test
public void testValidateStudyNumber() throws InvalidOptionException {
String in = generateTestStringOfLength(3);
assertEquals("validateStudyNumber must accept valid three digit inputs from the alphabet [a-zA-Z0-9].",
in, InputValidator.validateStudyNumber(in));
}
/**
* Test, whether {@link InputValidator#validateStudyNumber(String)} fails for inputs of correct length, but from
* illegal characters.
*
* @throws InvalidOptionException expected
*/
@Test(expected = InvalidOptionException.class)
public void testValidateStudyNumberFailsForInvalidAlphabet() throws InvalidOptionException {
String in = "7*A";
// -> InvalidOptionException
InputValidator.validateStudyNumber(in);
}
/**
* Test, whether {@link InputValidator#validateStudyNumber(String)} fails for Strings of length 0, 1 or two from
* the allowed alphabet.
*
* @throws InvalidOptionException expected.
*/
@Test(expected = InvalidOptionException.class)
public void testValidateStudyNumberFailsForInputOfInsufficientLength() throws InvalidOptionException {
String in = generateTestStringOfLength(RAND.nextInt(3));
// -> InvalidOptionException
InputValidator.validateStudyNumber(in);
}
/**
* Test, whether {@link InputValidator#validateStudyNumber(String)} fails for input Strings which are too long.
*
* @throws InvalidOptionException expected.
*/
@Test(expected = InvalidOptionException.class)
public void testValidateStudyNumberFailsForLongerInput() throws InvalidOptionException {
String in = generateTestStringOfLength(RAND.nextInt(50) + 4);
// -> InvalidOptionException
InputValidator.validateStudyNumber(in);
}
/*
Generates a String of length len which consists of letters from the alphabet [a-zA-Z0-9].
*/
private static String generateTestStringOfLength(int len) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append(ALPHABET.charAt(RAND.nextInt(ALPHABET.length())));
}
return sb.toString();
}
/**
* Test, whether {@link InputValidator#validateSampleType(String)} returns the expected {@link SampleType} for
* given input strings.
*
* @throws InvalidOptionException NOT expected
*/
@Test
public void testValidateSampleType() throws InvalidOptionException {
for (SampleType t : SampleType.values()) {
assertEquals("validateSampleType didn't return expected value for input \"" + t.toString() + "\"",
t, InputValidator.validateSampleType(t.toString()));
}
}
/**
* Test, whether {@link InputValidator#validateSampleType(String)} throws a {@link InvalidOptionException} for
* unknown sample types (in this case {@code x}).
*
* @throws InvalidOptionException expected
*/
@Test(expected = InvalidOptionException.class)
public void testValidateSampleTypeFailsForUnknownInputs() throws InvalidOptionException {
// -> InvalidOptionException
InputValidator.validateSampleType("x");
}
/**
* Test, whether {@link InputValidator#validateNumberOfCodes(String)} returns the expected {@link Integer}.
* In this method, posit
*
* @throws InvalidOptionException NOT expected
*/
@Test
public void testValidateNumberOfCodes() throws InvalidOptionException {
// nextInt(bounds) returns a number between 0 (inclusive) and bound (exclusive), so +1 should be safe.
int number = RAND.nextInt(Integer.MAX_VALUE) + 1;
String string = Integer.toString(number);
assertEquals("validateNumberOfCodes failed to validate input \"" + string + "\"",
number, InputValidator.validateNumberOfCodes(string));
}
/**
* Test, whether {@link InputValidator#validateNumberOfCodes(String)} throws a {@link InvalidOptionException}
* for negative numbers and 0.
*
* @throws InvalidOptionException expected
*/
@Test(expected = InvalidOptionException.class)
public void testValidateNumberOfCodesFailsForNegativeNumbers() throws InvalidOptionException {
int number = -RAND.nextInt(Integer.MAX_VALUE);
String string = Integer.toString(number);
// -> InvalidOptionException
InputValidator.validateNumberOfCodes(string);
}
/**
* Test, whether {@link InputValidator#validateNumberOfCodes(String)} throws a {@link InvalidOptionException}
* for non-numeric inputs, which cannot be parsed into an {@link Integer}.
*
* @throws InvalidOptionException expected
*/
@Test(expected = InvalidOptionException.class)
public void testValidateNumberOfCodesFailsForNonNumericInput() throws InvalidOptionException {
String string = "notNumeric";
// -> InvalidOptionException
InputValidator.validateNumberOfCodes(string);
}
/**
* Test, whether {@link InputValidator#validateNumberOfCodes(String)} throws a {@link InvalidOptionException}
* for inputs that exceed the range of an {@link Integer} ({@link Integer#MAX_VALUE}).
*
* @throws InvalidOptionException expected
*/
@Test(expected = InvalidOptionException.class)
public void testValidateNumberOfCodesFailsForNumbersTooLarge() throws InvalidOptionException {
// 10 * Integer.MAX_VALUE
String string = "21474836470";
InputValidator.validateNumberOfCodes(string);
}
}

View file

@ -0,0 +1,39 @@
package de.vanitasvitae.imi.codes;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import java.util.regex.Pattern;
import org.junit.Test;
/**
* Test functionality of the {@link SampleType} enum.
*/
public class SampleTypeTest {
private static final Pattern SAMPLE_TYPE_VALUE_PATTERN = Pattern.compile(InputValidator.ALPHABET);
/**
* Test, whether all {@link SampleType} names are of length 1 and from the alphabet [a-zA-Z0-9].
*/
@Test
public void testSampleTypeNames() {
for (SampleType t : SampleType.values()) {
assertTrue("SampleType value names must be one letter from the alphabet [a-zA-Z0-9].",
SAMPLE_TYPE_VALUE_PATTERN.matcher(t.toString()).matches());
}
}
/**
* Test, whether {@link SampleType#getEnum(String)} correctly maps {@link SampleType#name} to the corresponding
* values.
*/
@Test
public void testGetEnum() {
for (SampleType t : SampleType.values()) {
assertEquals("getEnum must return the SampleType, which corresponds to the given name.",
t, SampleType.getEnum(t.toString()));
}
}
}