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) intoDataReader.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"))); } }