One way to remove duplication is using inheritance. However, in many cases composition is better and just as easy to implement.
Consider the following code:
public class UpperCaseEmphasiser {
public String emphasise(String something) {
StringBuffer result = new StringBuffer();
StringTokenizer tokens = new StringTokenizer(something, " ", true);
while(tokens.hasMoreTokens()){
String token = tokens.nextToken();
if(!token.equals(" ")){
token = token.toUpperCase();
}
result.append(token);
}
return result.toString();
}
}
with the following tests:
import junit.framework.TestCase;
public class UpperCaseEmphasiserTest extends TestCase {
private UpperCaseEmphasiser emphasiser = new UpperCaseEmphasiser();
public void testThatEmphasiserDoesNotPutAnyEmphasisOnEmptyString() {
assertEquals("", emphasiser.emphasise(""));
}
public void testThatEmphasiserPutsEmphasisOnOneWord() {
assertEquals("HELLO", emphasiser.emphasise("hello"));
}
public void testThatEmphasiserPutsEmphasisOnMultipleWords() {
assertEquals("HELLO WORLD", emphasiser.emphasise("hello world"));
}
}
and another class called "AsteriskEmphasiser", which is exactly the same except having "token = ""token"";" instead of "token = token.toUpperCase();" and the tests for it being slightly different as appropriate.
There is a lot of duplication between "UpperCaseEmphasiser" and "AsteriskEmphasiser" and also between their tests. One way this duplication can be removed is using inheritance, creating an abstract class for the duplicated code and having the concrete subclasses containing only the code that is different:
public abstract class Emphasiser {
public String emphasise(String something) {
StringBuffer result = new StringBuffer();
StringTokenizer tokens = new StringTokenizer(something, " ", true);
while(tokens.hasMoreTokens()){
String token = tokens.nextToken();
if(!token.equals(" ")){
token = emphasiseWord(token);
}
result.append(token);
}
return result.toString();
}
abstract String emphasiseWord(String token);
}
and then UpperCaseEmphasiser becomes:
public class UpperCaseEmphasiser extends Emphasiser {
String emphasiseWord(String token) {
return token.toUpperCase();
}
}
and "AsteriskEmphasiser" is modified similarly.
Using inheritance has eliminated the duplication between the classes, but not between the tests. Sometimes, people will use inheritance in the tests to eliminate that duplication too, however, I suggest that using composition instead is better, because it separates the code into the functionality that is shared and the functionality that is different, in a way in which the functionality that is different can be called independently of the code that is shared. In this example, Emphasiser can be modified as:
public class Emphasiser {
private WordEmphasiser wordEmphasiser;
public Emphasiser(WordEmphasiser wordEmphasiser){
this.wordEmphasiser = wordEmphasiser;
}
private String emphasiseWord(String token){
return wordEmphasiser.emphasiseWord(token);
}
public String emphasise(String something) {
... unchanged ...
and UpperCaseEmphasiser becomes:
public class UpperCaseEmphasiser implements WordEmphasiser {
public String emphasiseWord(String token) {
return token.toUpperCase();
}
}
(and WordEmphasiser is:
public interface WordEmphasiser {
String emphasiseWord(String token);
}
)
and the tests can be kept the same by doing:
public class UpperCaseEmphasiserTest extends TestCase {
private Emphasiser emphasiser = new Emphasiser(new UpperCaseEmphasiser());
... unchanged ...
The code as shown so far isn't a big change from using inheritance, but does have a big effect. With this arrangement, instead of having the tests duplicated (or using inheritance in the tests to remove the duplication) the tests can now be split into two parts. One test for the class with the code that is common, and separate tests for the code that is different. The tests for the common code now becomes:
public class EmphasiserTest extends TestCase {
private MockWordEmphasiser aMockWordEmphasiser = new MockWordEmphasiser();
class MockWordEmphasiser implements WordEmphasiser {
public Stack expectedWords = new Stack();
public String emphasiseWord(String word) {
String expectedWord = (String)expectedWords.pop();
assertEquals(expectedWord, word);
return word;
}
}
private Emphasiser emphasiser = new Emphasiser(aMockWordEmphasiser);
public void testThatEmphasiserDoesNotPutAnyEmphasisOnEmptyString() {
Object ensureEmphasiseWordIsNotCalled = new Object();
aMockWordEmphasiser.expectedWords.add(ensureEmphasiseWordIsNotCalled);
assertEquals("", emphasiser.emphasise(""));
}
public void testThatEmphasiserPutsEmphasisOnOneWord() {
aMockWordEmphasiser.expectedWords.add("hello");
assertEquals("hello", emphasiser.emphasise("hello"));
}
public void testThatEmphasiserPutsEmphasisOnMultipleWords() {
aMockWordEmphasiser.expectedWords.add(0,"hello");
aMockWordEmphasiser.expectedWords.add(0,"world");
assertEquals("hello world", emphasiser.emphasise("hello world"));
}
}
which is completely independent of the implementations of any "WordEmphasiser". There are many other ways to implement "Mock" objects, but that is beyond the scope of this article. Here I've presented a simple implementation which I hope is clear as it does not need any knowledge or reference to anything outside of the code presented.
Note that now the test is testing that the "WordEmphasiser" is called with the correct arguments and the "Emphasiser" returns the words converted using whatever the "WordEmphasiser" returns (in this case, making no change to the words).
The tests for the "UpperCaseEmphasiserTest" are now very simple:
public class UpperCaseEmphasiserTest extends TestCase {
private WordEmphasiser emphasiser = new UpperCaseEmphasiser();
public void testThatEmphasiserPutsEmphasisOnOneWord() {
assertEquals("HELLO", emphasiser.emphasiseWord("hello"));
}
}
and similarly for "AsteriskEmphasiserTest". Note that now one test is sufficient, because the WordEmphasiser is only called for one word at a time - it isn't called for spaces or multiple words, because that is taken care of by the "Emphasiser". This is an example of a "separation of concerns"; i.e. the splitting of a string into words has been separated from the application of emphasis to each word. There are other benefits in addition to the simplification of the tests, and the removal of duplication. Using composition also allows more flexibility in combining these classes together. For example, there could be an "Emphasiser" which puts emphasis on every other word, which could reuse the "WordEmphasisers" independently of thier use by the standard "Emphasiser".
Whenever you see inheritance, particularly tests using inheritance, consider whether converting to composition would allow better separatation of concerns, making the tests clearer and the code cleaner.