Download tofu from ftp://ftp.reahl.org/tofu

Tofu

Introduction

Tofu is a testing framework, like unittest, or the J/Py/S-unit family.

Tofu differs from these test frameworks in that it allows you to have a hierarchy of test fixtures that is completely decoupled from your hierarchy of tests or test suites.

It all started as of a research experiment regarding the structure of testing code, but Tofu is now used in several inhouse projects.

Overview

Writing tests

Tofu tests are methods in a class that inherit from tofu.TestSuite. All methods in such a class must take the arguments: (self, fixture). When the tests are run, a fixture is set up, passed to the test that is run, and torn down after the test run. For example:

class SomeTests(tofu.TestSuite):
    def one(self, fixture):
        print 'test one: %s' % fixture.stuff

    def two(self, fixture):
        """docstrings work as with unittest"""
        assert None, 'something broke in test two'
  • Note how it is not necessary to use special names for test methods. With Tofu, all methods are tests, all tofu.TestSuite classes are test suites.

Writing fixtures

A fixture is a class which derives from tofu.Fixture:

class MyFixture(tofu.Fixture):
    def setUp(self):
        print 'setting up default fixture for this suite'
        self.stuff = 'this was set up in the fixture'

    def tearDown(self):
        print 'tearing down default fixture for this suite'

    def anotherMethod(self):
        """this one has no special meaning"""
        pass

Before each test method is run on a tofu.TestSuite derived class, an instance of such a tofu.Fixture class is constructed, and its setUp() method is called. After the test has been run, the tearDown() of the same fixture is called before the fixture instance itself is discarded.

Linking fixtures to tests

You can link a specific test (remember a test is a method in Tofu) to a specific fixture class using tofu.usefixture as a Python decorator:

class SomeTests(tofu.TestSuite):

    @tofu.usefixture(MyFixture)
    def one(self, fixture):
        print 'test one: %s' % fixture.stuff

    @tofu.usefixture(AnotherFixture)
    def two(self, fixture):
        """docstrings work as with unittest"""
        assert None, 'something broke in test two'

If you find that many of your test methods re-use the same fixture, you can specify a default fixture for Tofu to use by setting the class attribute Fixture on your test suite class. Using the tofu.usefixture decorator on test methods overrides the default fixture set this way:

class SomeTests(tofu.TestSuite):
    Fixture = MyDefaultFixture

    def one(self, fixture):
        print 'test one: %s' % fixture.stuff

    def two(self, fixture):
        """docstrings work as with unittest"""
        assert None, 'something broke in test two'

    @tofu.fixture(MySpecialFixture)
    def three(self, fixture):
        """this test method uses MySpecialFixture, not the default"""

Methods that are not tests

Sometimes its useful to have methods on a test suite class that should not be run as tests. They may be utility methods called by several tests, for example. The decorator tofu.skip can be used to mark such a method so that Tofu does not treat it as a test:

class SomeTests(tofu.TestSuite):
    Fixture = MyDefaultFixture

    def one(self, fixture):
        print 'test one: %s' % fixture.stuff

    @tofu.skip
    def iamnotatest(self):
        print 'i am not a test method, and should not run with the tests'

Running your tests

You can run your tests in two ways:

The tofu script
Tofu includes a script which you can run, giving it the tests it should run on the command line using a special syntax. Run tofu --help to see its syntax and options.
Using python setup.py tofu
Tofu also adds a command to setup.py (if you use setuptools). You can run python setup.py tofu --help on your setup.py to see its usage information.

Both of these methods allow you to also run the tests under the watching eyes of the python debugger.

Experimental features

A tofu.ChainedFixture is a special kind of fixture which depends upon another fixture. If B is a ChainedFixture which is chained to fixture class A, test cases using B will always first call A.setUp, followed by B.setUp. Then the test will run, and the tearDowns will be called in reverse order.

An example illustrates its usage:

class Other(tofu.Fixture):
    def setUp(self):
        self.addSomething = 'something'

class Chained(tofu.ChainedFixture):
    chainedTo = Other
    def setUp(self):
        print 'setting up the chained fixture'
        self.addedSomething = 'some stuff set up in the chained fixture'
    def tearDown(self):
        print 'tearing down chained fixture'

Attributes of fixtures downstream in the chain of fixtures will be available as if they were attributes set on the chained fixure passed to the test.

Chained fixtures provide a means to re-use an existing fixture without having to inherit from it. It is an experimental feature which is not much used in real life...