A Simple FIT C++ Example

 

Michael Feathers

mfeathers@objectmentor.com

 

 

FIT essentially revolves around classes called Fixtures.  In the typical sequence of events, you take an HTML document that contains tables describing test data and run it through FIT.  FIT gives you back a file that looks just like the one you fed it, except the tables now have annotations and colors which show you whether your tests passed or failed when run against your application.  Fixtures are the classes which traverse the tables and communicate with the application you are testing.

 

Ward Cunningham discovered an ingenious way of allowing developers to use standard fixtures and still have maximum flexibility when creating their own custom fixtures.  He came up with a concrete class named Fixture which serves as the base for all fixtures.  By default it traverses the contents of an HTML file and produces output that looks like the same as the input, except for one thing: the cells that contain values are colored with gray, the ignore color.

 

Let’s take a look at an example:

 

Here is a table that uses the default class Fixture:

 

Fixture

 

 

 

 

1

1

2

2

2

1

3

2

2

2

 

If you haven’t run this document through FIT, the table should just like a simple 5 x 2 table without any colors.  Run this document through FIT and take a look at it.  You should see the last two rows in gray.

 

Column Fixtures

 

You might be wondering how to get FIT to do something real.  Let’s take a look at that.  Fixture has a subclass named ColumnFixture.  It is just like Fixture except for one thing.  When you run it, it treats the first row specially.  It looks at the contents of the cells and takes them to be the names of members or member functions on the fixture class.  If a name is a member variable name then values in its column are taken to be values that are read into that member in an instance of the fixture.  If the name is a member function name, the values in that column are taken to be expected values for a call to that function.  Here is an example ColumnFixture:

 

ColumnFixture

 

 

 

 

1

1

2

2

2

1

3

2

2

2

 

 

 

 

 

 

You can run this fixture if you want to, but the results will look a little weird.  You will see error messages in the second row that tell you that an adapter couldn’t be found for the names “1” and “2.”  And, that makes sense.  There are no variables or methods named “1” and “2” in the ColumnFixture class.  First of all, a C++ compiler wouldn’t allow us to name things using numbers and secondly, you have to do some special work in C++ to provide access to members.

 

The best way to do this is to create a subclass of ColumnFixture.  Here is one named Calculator:

 

#include <ColumnFixture.h>

 

class Calculator : public ColumnFixture

{

public:

                        int value1, value2;

 

                        double sum() { return value1 + value2; }

                        double difference() { return value1 - value2; }

                        double product() { return value1 * value2; }

                        double quotient() { return value1 / value2; }

 

                        Calculator() {

                                    PUBLISH(Calculator,int,value1);

                                    PUBLISH(Calculator,int,value2);

                                    PUBLISH(Calculator,double,sum);

                                    PUBLISH(Calculator,double,difference);

                                    PUBLISH(Calculator,double,product);

                                    PUBLISH(Calculator,double,quotient);

                        }

};

 

 

Calculator is a fixture that provides access to two variables and four functions.  The PUBLISH macro uses deep, embarrassing C++ magic to create a little object known as a type adapter that is used by the framework to push values in and out of variables and call functions.

 

Here is a little table that uses Calculator:

 

 

Calculator.Calculator

 

 

 

 

value1

value2

sum

product

difference

1

1

2

1

0

12

12

24

100.9

0

 

 

 For kicks, you should run it.  Notice how the error is flagged with red and a little message that shows what the expected and actual values are.

 

It seems hard to believe that the little bit of code above works that magic against the table.  It turns out that the reason that we can do all of that work with Calculator is because it lets Fixture and ColumnFixture do a lot of work for us.

 

Let’s take a look at some of the key functions of Fixture:

 

class Fixture

{

public:

                                                                        Fixture();

            virtual                                                   ~Fixture();

 

            virtual TypeAdapter                              *adapterFor(const string& member) const;

            virtual bool                                           hasAdapterFor(const string& member) const;

 

            virtual Fixture                                        *makeFixture(const string& name) const;

 

            virtual void                                            doTables(ParsePtr tables);

            virtual void                                            doTable(ParsePtr table);

            virtual void                                            doRows(ParsePtr rows);

            virtual void                                            doRow(ParsePtr row);

            virtual void                                            doCells(ParsePtr cells);

            virtual void                                            doCell(ParsePtr cell, int columnNumber);

 

            virtual void                                            handleException(ParsePtr cell, exception& e);

            virtual void                                            check(ParsePtr cell, TypeAdapter *a);

           

};

 

The listing above shows some of the primary functions in the Fixture class.  There some things that should look mysterious, functions dealing with adapters for instance, but the real work happens in the series of functions named doTables, doTable, doRows, doRow, etc.  Each of those processing methods accepts a pointer to an element of a HTML parse tree, and in general, each of those functions calls the next one down in the sequence.  For instance, doTables calls doTable as longs are there are more tables in a document, and doTable calls doRows and doRows calls doRow as long as there are more rows in a table.  It is a very elegant little set of calls ending in calls to the method doCell.  In Fixture, doCell simply calls a method named ignore which adds some text to the current cell (the parse node passed to it) to make it show up as gray when it is displayed.

 

What is very nice is that you can create subclasses of Fixture which override any of those methods and do additional work.  For instance, ColumnFixture overrides the doRows function to do something special.  First of all, lets look at the doRows function for Fixture:

 

void Fixture::doRows(ParsePtr rows)

{

    while (rows) {

        doRow(rows);

        rows = rows->more;

    }

}

 

That is simple enough.  In this case, doRows traverses a list of parse nodes that represent rows (they are connected by a member variable on Parse named more) and calls doRow for each of them.

 

Let’s see what ColumnFixture does:

 

void ColumnFixture::doRows(ParsePtr rows)

{         

            bind(rows->parts);

            Fixture::doRows(rows->more);

}

 

 

ColumnFixture calls a special function named bind on the first row (parts points to the next cell in a row) and then calls Fixture’s doRows on all of the rows after the first.  The bind function looks up the text in each cell in the row and saves a set of things called type adapters so that they can be looked at as other cells are read.  Bind actually looks like this:

 

void ColumnFixture::bind (ParsePtr heads)

{

    for (int i=0; heads; i++, heads = heads->more) {

                        columnBindings.push_back(0);

        string name = heads->text();

        string suffix = "()";

                    try {

            if (endsWith(name, suffix)) {

                string methodName = name.substr(0,name.size() - suffix.size());

                    columnBindings[i] = getTargetClass()->adapterFor(methodName);

             }

             else {

     columnBindings[i] = getTargetClass()->adapterFor(name);

             }

        }

        catch (exception& e) {

            handleException (heads, e);

        }

    }

}

 

The vector columnBindings is a member variable of the ColumnFixture class.  It comes in handy because ColumnFixture also overrides the doCell function of ColumnFixture.  Here is Fixture’s version:

 

void Fixture::doCell(ParsePtr cell, int columnNumber)

{

            ignore(cell);

}

 

Short and sweet.

 

In ColumnFixture, doCell does more work.  If the adapter is for an “object” (a member variable), the variable is set via the adapter.  If the adapter is for a member function (not an object) the cell is checked using the current adapter.  This corresponds roughly to our description of what column fixtures do.  Notice as well how doCell uses columnBindings to process the text of a cell.  You can do a lot of useful things by overriding Fixture functions.

 

void ColumnFixture::doCell(ParsePtr cell, int column)

{

    TypeAdapter *a = columnBindings[column];

    try {

        if (a == 0) {

            ignore(cell);

        } else if (a->isObject()) {

            string text = cell->text();

            if (text != "") {

                a->set(text);

            }

        } else if (!a->isObject()) {

            check(cell, a);

        }

    } catch(exception& e) {

        handleException(cell, e);

    }

}