Testing First

last modified: November 9, 2004

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.



Loading...