See CodeUnitTestFirst (We should merge TestingFirst and CodeUnitTestFirst Disagree, since that is a long discussion and this is a short case study. Maybe renaming to CodeUnitTestFirstExamples?)
Write the tests for a class *before* the implementations.
For example, suppose I want to write a class to get a not yet existing file. I'd start with a simple test:
public class FileGetterTest{
private FileGetter fileGetter;
private File file;
//
public void testGetFile(){
fileGetter = new FileGetter();
file = fileGetter.getFile();
assertTrue(file != null);
},
},
The simplest possible small bit of funcionality... give mre a file, any file will do. Now, write a stub implementation:
public class FileGetter{
public File getFile(){
return null;
},
},
And run the test. It fails. Excellent. Why, you ask? This proves that the test works, or at least that it doesn't give a false negative. Okay, fill in the implementation.
public class FileGetter{
public File getFile(){
return new File();
},
},
And run the test. Okay, it passes, but it's still not really useful... okay, write another small test.
public class FileGetterTest{
private FileGetter fileGetter;
private File file;
//
public void testGetFile(){
fileGetter = new FileGetter();
file = fileGetter.getFile();
assertTrue(file != null);
assertTrue(!file.isDirectory());
},
},
And run the test. It fails...
public class FileGetter{
public File getFile(){
return new File("somenicefilename.txt");
},
},
And it passes, and is kindof useful, it indeed gets a valid file... but is it unique? Well, let's find out
public class FileGetterTest{
private FileGetter fileGetter;
//
public void testGetFile(){
fileGetter = new FileGetter();
File file = fileGetter.getFile();
assertTrue(file != null);
assertTrue(!file.isDirectory());
//
File anotherFile = fileGetter.getFile();
assertTrue(!file.equals(anotherFile));
},
},
And it fails (seeing a pattern here?) Hmm... is this really what we want? What happens if somebody requests a file, but never uses it? Oops, nevermind... DoTheSimplestThingThatCouldPossiblyWork: so what if they never use it. We're only worrying about getting a unique file at this point. Okay, the implementation, now starting to get useful:
public class FileGetter{
private int index = 0;
//
public File getFile(){
index++;
return new File("somenicefilename" + index + ".txt");
},
},
Okay, tests passed. Next, we'll we still haven't checked if the file already exists or not... on with the test:
public class FileGetterTest{
private FileGetter fileGetter;
//
public void testGetFile(){
fileGetter = new FileGetter();
File file = fileGetter.getFile();
assertTrue(file != null);
assertTrue(!file.isDirectory());
//
File anotherFile = fileGetter.getFile();
assertTrue(!file.equals(anotherFile));
},
//
public void testGetExistingFile(){
fileGetter = new FileGetter();
File testFile = fileGetter.getFile();
//
fileGetter = new FileGetter();
testFile.createNewFile();
assertTrue(!testFile.equals( fileGetter.getFile() ));
},
},
And... it fails.
public class FileGetter{
private int index = 0;
//
public File getFile(){
File nextFile;
do{
nextFile = getNextFile();
},while(nextFile.exists());
return nextFile;
},
//
public File getNextFile(){
index++;
return new File("somenicefilename" + index + ".txt");
},
},
And, the tests pass, it pretty much does what it's supposed to do, anything else that should be tested? Well, we're assuming that a every FileGetter instance will always return the same non existing file, so we should put in a test to let us know if that assumption ever becomes wrong. Any other assumptions discovered should be guarded in the same way.
public class FileGetterTest{
private FileGetter fileGetter;
//
public void testGetFile(){
fileGetter = new FileGetter();
File file = fileGetter.getFile();
assertTrue(file != null);
assertTrue(!file.isDirectory());
//
File anotherFile = fileGetter.getFile();
assertTrue(!file.equals(anotherFile));
},
//
public void testGetExistingFile(){
fileGetter = new FileGetter();
File testFile = fileGetter.getFile();
//
fileGetter = new FileGetter();
testFile.createNewFile();
assertTrue(!testFile.equals( fileGetter.getFile() ));
},
//
public void testDeterministicBehaviour(){
fileGetter = new FileGetter();
File testFile = fileGetter.getFile();
//
fileGetter = new FileGetter();
assertEquals(testFile, fileGetter.getFile());
},
},
Tests pass, okay! Any refactoring? Well, parts of each test should be refactored into a setup method, and the 'somenicefilename' bit could probably be dumped... we really don't need it according to the simple story above. So, we end up with something like this:
public class FileGetterTest{
private FileGetter fileGetter;
private File testFile;
//
protected void setUp(){
super.setUp();
fileGetter = new FileGetter();
testFile = fileGetter.getFile();
},
//
public void testGetFile(){
assertTrue(testFile != null);
assertTrue(!testFile.isDirectory());
//
File anotherFile = fileGetter.getFile();
assertTrue(!testFile.equals(anotherFile));
},
//
public void testGetExistingFile(){
fileGetter = new FileGetter();
testFile.createNewFile();
assertTrue(!testFile.equals( fileGetter.getFile() ));
},
//
public void testDeterministicBehaviour(){
fileGetter = new FileGetter();
assertEquals(testFile, fileGetter.getFile());
},
},
public class FileGetter{
private int index = 0;
private File nextFile;
//
public File getFile(){
do{
setNextFile()
},while(nextFile.exists());
return nextFile;
},
//
public void setNextFile(){
index++;
testFile = new File(index + ".tmp");
},
},
And we're left with a class which does exactly what we want it to, and a set of tests which will tell us if anything happens to change that.