I cannot refactor the code, I must test how it is written. I cannot figure out how to test the if (reader.hasNextLong()) line of code:
public class UnitsConvertor {
// No instances please. All methods are static.
private UnitsConvertor() {
}
public static void main(String[] args) {
// parses user input, checking both integer and string
System.out.println("Please Enter the input value followed by the unit:");
Scanner reader = new Scanner(System.in);
if (reader.hasNextLong()) {
long number = reader.nextLong();
if (reader.hasNext("\\b+(mil|in|inch|ft|foot|feet|yd|yard|mi|mile)\\b+")) {
String unit = reader.findInLine("\\b+(mil|in|inch|ft|foot|feet|yd|yard|mi|mile)\\b+");
double mm = toMm(number, unit);
System.out.println(number + " " + unit + " is:");
System.out.println(String.format("%f", mm) + " mm");
System.out.println(String.format("%f", mm / 10) + " cm");
System.out.println(String.format("%f", mm / 1000) + " m");
System.out.println(String.format("%f", mm / 1000000) + " km");
} else if (reader.hasNext("\\b+(mm|millimeter|cm|centimeter|m|meter|km|kilometer)\\b+")) {
String unit = reader.findInLine("\\b+(mm|millimeter|cm|centimeter|m|meter|km|kilometer)\\b+");
double mil = toMil(number, unit);
System.out.println(number + " " + unit + " is:");
System.out.println(String.format("%.2g", mil) + " mil");
System.out.println(String.format("%.2g", mil / 1000) + " inch");
System.out.println(String.format("%.2g", mil / 12000) + " ft");
System.out.println(String.format("%.2g", mil / 36000) + " yard");
System.out.println(String.format("%.2g", mil / 63360000) + " mile");
} else {
System.out.println("Invalid input");
}
} else {
System.out.println("Invalid input");
}
reader.close();
return;
}
// convert any metric system with unit specified in second parameter to mil
public static double toMil(long metric, String unit) {
double mm;
if (unit.matches("\\b+(mm|millimeter)\\b+")) {
mm = metric;
} else if (unit.matches("\\b+(cm|centimeter)\\b+")) {
mm = metric * 10;
} else if (unit.matches("\\b+(m|meter)\\b+")) {
mm = metric * 1000;
} else if (unit.matches("\\b+(km|kilometer)\\b+")) {
mm = metric * 1000000;
} else {
throw new IllegalArgumentException("Bad unit value");
}
return mm * 39.3701;
}
// convert any imperial system with unit specified in second parameter to mm
public static double toMm(long imperial, String unit) {
double mil;
if (unit.matches("\\b+(in|inch)\\b+")) {
mil = imperial * 1000;
} else if (unit.matches("\\b+(ft|foot|feet)\\b+")) {
mil = imperial * 12000;
} else if (unit.matches("\\b+(yd|yard)\\b+")) {
mil = imperial * 36000;
} else if (unit.matches("\\b+(mi|mile)\\b+")) {
mil = imperial * 63360000;
} else if (unit.matches("\\b+mil\\b+")) {
mil = imperial;
} else {
throw new IllegalArgumentException("Illegal unit value.");
}
return mil * 0.0254;
}
}
Without the testHasNextLong() I reach 98.2% of code coverage. The only yellow and red highlights from code coverage show that hasNextLong(), the else if (reader.hasNext("\b+(mm|millimeter|cm|centimeter|m|meter|km|kilometer)\b+")), and the 2 "elses" that contain System.out.println("Invalid input"); are not covered. When I add in the hasNextLong test only 10/23 tests are run. Without it 22/22 are run.
Here are all the tests I have written:
class UnitsConvertorTest {
private final InputStream systemIn = System.in;
private final PrintStream systemOut = System.out;
private ByteArrayInputStream testIn;
private ByteArrayOutputStream testOut;
private String userUnitInput;
private Long userValueInput;
/**
* @throws java.lang.Exception
*/
@BeforeEach
void setUp() throws Exception {
testOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(testOut, true));
}
/**
* @throws java.lang.Exception
*/
@AfterEach
void tearDown() throws Exception {
System.setIn(systemIn);
System.setOut(systemOut);
}
@Test
public void testHasNextLong() {
final String testString = "10 mil";
System.setIn(new ByteArrayInputStream(testString.getBytes()));
Scanner scanner = new Scanner(systemIn);
System.out.println("" + scanner.hasNextLong());
assertTrue(scanner.hasNextLong());
scanner.close();
System.setIn(systemIn);
}
// we have a long and a string
// test when string = mm
@Test
void mmTest() {
userUnitInput = "mm";
userValueInput = (long) 10;
assertEquals(393.701, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = millimeter
@Test
void millimeterTest() {
userUnitInput = "millimeter";
userValueInput = (long) 10;
assertEquals(393.701, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = cm
@Test
void cmTest() {
userUnitInput = "cm";
userValueInput = (long) 10;
assertEquals(3937.01, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = centimeter
@Test
void centimeterTest() {
userUnitInput = "centimeter";
userValueInput = (long) 10;
assertEquals(3937.01, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = m
@Test
void mTest() {
userUnitInput = "m";
userValueInput = (long) 10;
assertEquals(393701, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = meter
@Test
void meterTest() {
userUnitInput = "meter";
userValueInput = (long) 10;
assertEquals(393701, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = km
@Test
void kmTest() {
userUnitInput = "km";
userValueInput = (long) 10;
assertEquals(393701000, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = kilometer
@Test
void kilometerTest() {
userUnitInput = "kilometer";
userValueInput = (long) 10;
assertEquals(393701000, UnitsConvertor.toMil(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = in
@Test
void inTest() {
userUnitInput = "in";
userValueInput = (long) 10;
assertEquals(254, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = inch
@Test
void inchTest() {
userUnitInput = "inch";
userValueInput = (long) 10;
assertEquals(254, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = ft
@Test
void ftTest() {
userUnitInput = "ft";
userValueInput = (long) 10;
assertEquals(3048, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = foot
@Test
void footTest() {
userUnitInput = "foot";
userValueInput = (long) 10;
assertEquals(3048, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = feet
@Test
void feetTest() {
userUnitInput = "feet";
userValueInput = (long) 10;
assertEquals(3048, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = yd
@Test
void ydTest() {
userUnitInput = "yd";
userValueInput = (long) 10;
assertEquals(9144, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = yard
@Test
void yardTest() {
userUnitInput = "yard";
userValueInput = (long) 10;
assertEquals(9144, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = mi
@Test
void miTest() {
userUnitInput = "mi";
userValueInput = (long) 10;
assertEquals(16093440, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = mile
@Test
void mileTest() {
userUnitInput = "mile";
userValueInput = (long) 10;
assertEquals(16093440, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// we have a long and a string
// test when string = mil
@Test
void milTest() {
userUnitInput = "mil";
userValueInput = (long) 10;
assertEquals(.254, UnitsConvertor.toMm(userValueInput, userUnitInput));
}
// Testing IllegalArgumentException when a user enters a decimal value
@Test
void testExpectedExceptionToMil() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
UnitsConvertor.toMil(10, "mizx");
});
}
@Test
void testExpectedExceptionToMm() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
UnitsConvertor.toMm(10, "mike");
});
}
// normalizeExpectedOutput - generate the eol character at run-time. // then
// there is no need to hard-code "\r\n" or "\n" for eol
// and string comparisons are portable between Windows, macOS, Linux.
public String normalizeExpectedOutput(String expectedOutput) {
String normExpectedOutput;
String[] outputs = expectedOutput.split("\n");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
for (String str : outputs) {
pw.println(str);
}
pw.close();
normExpectedOutput = sw.toString();
return normExpectedOutput;
}
@Test
// test that user input returns calculation conversions correctly
public void testMainToMm() {
String inputValueAndUnit = "10 mil";
Long inputValue = 10L;
// save current System.in and System.out
InputStream myIn = new ByteArrayInputStream(inputValueAndUnit.getBytes());
System.setIn(myIn);
double mm = UnitsConvertor.toMm(inputValue, "mil");
final String unNormalizedExpectedOutput = "Please Enter the input value followed by the unit:\n"
+ inputValueAndUnit + " is:\n" + String.format("%f", mm) + " mm\n" + String.format("%f", mm / 10)
+ " cm\n" + String.format("%f", mm / 1000) + " m\n" + String.format("%f", mm / 1000000) + " km";
final String expectedOutput = normalizeExpectedOutput(unNormalizedExpectedOutput);
UnitsConvertor.main(null);
// Check results
final String printResult = testOut.toString();
assertEquals(expectedOutput, printResult);
}
@Test
// get invalid number
public void testMainToMil() {
String inputValueAndUnit = "10 mm";
Long inputValue = 10L;
// save current System.in and System.out
InputStream myIn = new ByteArrayInputStream(inputValueAndUnit.getBytes());
System.setIn(myIn);
double mil = UnitsConvertor.toMil(inputValue, "mm");
final String unNormalizedExpectedOutput = "Please Enter the input value followed by the unit:\n"
+ inputValueAndUnit + " is:\n" + String.format("%.2g", mil) + " mil\n"
+ String.format("%.2g", mil / 1000) + " inch\n" + String.format("%.2g", mil / 12000) + " ft\n"
+ String.format("%.2g", mil / 36000) + " yard\n" + String.format("%.2g", mil / 63360000) + " mile";
final String expectedOutput = normalizeExpectedOutput(unNormalizedExpectedOutput);
UnitsConvertor.main(null);
// Check results
final String printResult = testOut.toString();
assertEquals(expectedOutput, printResult);
}
}