OVERVIEW
This post illustrates how we can easily write a better test case without polluting our production code with non-production code by performing a minor refactoring to the production code.
PROBLEM
Let’s assume we have a simple Data Reader that reads all the lines of a given algorithm data file and returns them:-
public class DataReader {
public List<string> getDataLines(AlgorithmEnum algorithm) {
// we have `StS-data.txt`, `CtE-data.txt` and `TtI-data.txt` under `src/main/resources` dir
String fileName = String.format("%s-data.txt", algorithm.getShortName());
Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(fileName));
List<string> list = new ArrayList<string>();
while (scanner.hasNextLine()) {
list.add(scanner.nextLine());
}
return list;
}
}
This API accepts an AlgorithmEnum and it looks something like this:-
public enum AlgorithmEnum {
SKIN_TO_SKIN("StS"),
CLOSURE_TO_EXIT("CtE"),
TIME_TO_INCISION("TtI");
private String shortName;
AlgorithmEnum(String shortName) {
this.shortName = shortName;
}
public String getShortName() {
return shortName;
}
}
Let’s assume each algorithm data file has millions of data lines.
So, how do we test this code?
SOLUTION 1: Asserting Actual Line Count == Expected Line Count
One straightforward way is to:-
- Pass in one of the Enum constants (AlgorithmEnum.SKIN_TO_SKIN, etc) into DataReader.getDataLines(..)
- Get the actual line counts
- Assert the actual line counts against the expected line counts
public class DataReaderTest {
@Test
public void testGetDataLines() {
List<string> lines = new DataReader().getDataLines(AlgorithmEnum.SKIN_TO_SKIN);
assertThat(lines, hasSize(7500));
}
}
This is a pretty weak test because we only check the line counts. Since we are dealing with a lot of data lines, it becomes impossible to verify the correctness of each data line.
SOLUTION 2: Adding a Test Constant to AlgorithmEnum
Another approach is to add a test constant to AlgorithmEnum:-
public enum AlgorithmEnum {
SKIN_TO_SKIN("StS"),
CLOSURE_TO_EXIT("CtE"),
TIME_TO_INCISION("TtI"),
// added a constant for testing purpose
TEST_ABC("ABC");
private String shortName;
AlgorithmEnum(String shortName) {
this.shortName = shortName;
}
public String getShortName() {
return shortName;
}
}
Now, we can easily test the code with our test data stored at src/test/resources/ABC-data.txt:-
public class DataReaderTest {
@Test
public void testGetDataLines() {
List<string> lines = new DataReader().getDataLines(AlgorithmEnum.TEST_ABC);
assertThat(lines, is(Arrays.asList("line 1", "line 2", "line 3")));
}
}
While this approach works, we pretty much polluted our production code with non-production code, which may become a maintenance nightmare as the project grows larger in the future.
SOLUTION 3: AlgorithmEnum Implements an Interface
Instead of writing a mediocre test case or polluting the production code with non-production code, we can perform a minor refactoring to our existing production code.
First, we create a simple interface:-
public interface Algorithm {
String getShortName();
}
Then, we have AlgorithmEnum to implement Algorithm:-
public enum AlgorithmEnum implements Algorithm {
SKIN_TO_SKIN("StS"),
CLOSURE_TO_EXIT("CtE"),
TIME_TO_INCISION("TtI");
private String shortName;
AlgorithmEnum(String shortName) {
this.shortName = shortName;
}
public String getShortName() {
return shortName;
}
}
Now, instead of passing AlgorithmEnum into getDataLines(…), we will pass in Algorithm interface.
public class DataReader {
public List<string> getDataLines(Algorithm algorithm) {
String fileName = String.format("%s-data.txt", algorithm.getShortName());
Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(fileName));
List<string> list = new ArrayList<string>();
while (scanner.hasNextLine()) {
list.add(scanner.nextLine());
}
return list;
}
}
With these minor changes, we can easily unit test the code with our mock data stored under src/test/resources directory.
public class DataReaderTest {
@Test
public void testGetDataLines() {
List<string> lines = new DataReader().getDataLines(new Algorithm() {
@Override
public String getShortName() {
// we have `ABC-data.txt` under `src/test/resources` dir
return "ABC";
}
});
assertThat(lines, is(Arrays.asList("line 1", "line 2", "line 3")));
}
}
Leave a Reply