Testing First

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.



EditText of this page (last edited November 9, 2004) or FindPage with title or text search