Thursday, January 04, 2007

Using Seams for Test Cases

I promised I'd write something techie one of these days so here it is.  It's a technique I've been using to write unit tests against code that has a problem with dependencies.  Many times this is a third party system or a database with a variable data set that you can't write a good case against.  Michael C. Feathers describes this as a Seam in Working Effectively with Legacy Code (a fantastic book -- buy it).

So let's say you have a class something like this...

    public class Report {
        public Integer[] fetchDataSet() {
            Integer[] lDataSet = null;
            // Go to the DB and fetch the data set (use your imagination here...lol)
            return lDataSet;
        }
       
        public int sumDataSet() {
            Integer[] lDataSet = fetchDataSet();
            int sum = 0;
            for (int i = 0; i < lDataSet.length; i++) {
                sum += lDataSet[i].intValue();               
            }
            return sum;
        }
    }

...and you need to write a test for the sumDataSet() method.  Normally you would write a JUnit test something like this:

    public void testSumDataSet() {
        Report report = new Report();
        int sum = report.sumDataSet();
       
        assertFalse(sum == 0);
    }

The two main problems with this is that it has a dependency on the database and you might not know what the values in the database are going to be so it's hard to make a proper assert.  There may be some cases where a sum of 0 is a valid sum so the assert in the example isn't a valid assert.

Fortunately, this problem is easily solved.  All you need to do is subclass your Report object and replace the fetchDataSet() method with one of your own that returns a set of known values.  It looks like this:

    public class ReportTest extends Report {
        public Integer[] fetchDataSet() {
            Integer[] dataSet = new Integer[2];
            dataSet[0] = new Integer(1);
            dataSet[1] = new Integer(5);
            return dataSet;
        }
    }

Now you can write your test case like this:

    public void testSumDataSetWithoutDB() {
        Report report = new ReportTest();
        int sum = report.sumDataSet();
        
        assertTrue(sum == 6);
    }    

And voila!!  A true test of your sumDataSet() method that has no dependencies and a set of known values you can write a true assert against.  It's a real unit test!  Yes!!

If you have any questions please refer to the Feathers book.  Every group that has legacy code should have a copy of it.  Buy it, read it, live it.