Should we aim for DRY, in the sense that changes to the functionality affect as little code as possible, our predictability, in the sense that operation of the code is trivial, when writing unit tests? Basically I'm asking about the trade-off between creating helper methods that are very general and that can be used by multiple unit tests versus only ever constraining the testing code to a single unit test. As an example take the case of a factory which has the following method signature:
public Node buildNode(String path, String name, Map<String, Object> attributes);
Depending on the parameters provided the resultant Node object will be different, and as such we need to test the different possibilities. If we aim for predictability we might write two self-contained unit tests as given in the first example, but if we aim for DRY we'd rather add a common helper method such as in the second example:
EXAMPLE1:
@Test
public void testBuildBasicNode() {
Node node = testee.buildNode("/home/user", "Node", null);
assertEquals("/home/user/Node", node.getAbsolutePath());
assertEquals(false, node.isFolder());
}
@Test
public void testBuildAdvancedNode() {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("type", NodeType.FOLDER);
Node node = testee.buildNode("/home/user", "Node", attributes);
assertEquals("/home/user/Node", node.getAbsolutePath());
assertEquals(true, node.isFolder());
}
EXAMPLE2:
@Test
public void testBuildBasicNode() {
Node node = testee.buildNode("/home/user", "Node", null);
Node comparisonNode = buildComparisonNode("/home/user", "Node", null);
assertEquals(comparisonNode, node);
}
@Test
public void testBuildAdvancedNode() {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("type", NodeType.FOLDER);
Node node = testee.buildNode("/home/user", "Node", attributes);
Node comparisonNode = buildComparisonNode("/home/user", "Node", attributes);
assertEquals(comparisonNode, node);
}
private Node buildComparisonNode(String path, String name, Map<String, Object> attributes) {
// Not just a duplicate of the buildNode method,
// can be more trivial if we only limit it to unit tests that share some common attributes
...
}
My problem with the first example (predictability) is that if any functionality changes (like say how the AbsolutePath should be formatted), it requires changes across all my unit tests. My problem with the second example is that buildComparisonNode feels like something which should be tested as well, and I certainly don't want to start writing tests for tests.
Also, as a closing thought, would you declare final variables for the literal Strings used in the example unit tests, or are they fine as they are?