Tofu

Tofu is a collection of test utilities, some of which are integrated to work as a plugin with pytest.

The main feature of Tofu is its ability to let you write of test Fixture classes, each of which contains a collection of related objects used in a test.

Tofu also gobbled up another little test project, called tut which implemented a few utilities for testing exceptions, dealing with temporary files, etc. All this functionality is now also part of Tofu.

Test Fixtures

Fixture

class reahl.tofu.Fixture

A test Fixture is a collection of objects defined and set up to be used together in a test.

A Fixture can be used by many tests and also by other Fixtures.

To create your own Fixture, create a subclasses of Fixture. On such a subclass, a new member of the Fixture is defined by a specially named method that is able to create the object.

The name of such a ‘factory method’ is new_ with the name of the object appended.

class MyFixture(Fixture):
    def new_name(self):
        return 'myname'

A Fixture is used inside a with statement or as using a plugin for a test framework.

A member of the Fixture can be accessed by referencing it by name on the fixture as if it were an attribute.

The first time a program references a member on the Fixture instance, the corresponding new_ method will be called behind the scenes (without arguments) to create the object. Subsequent accesses of the same member will always bring back the same instance which was created on the first access.

with MyFixture() as fixture:
     assert fixture.name is fixture.name

If the created singleton object also needs to be torn down, the new_ method should yield it (not return), and perform necessary tear down after the yield statement.

Singletons are torn down using this mechanism in reverse order of how they were created. (The last one created is torn down first.) Singleton instances are also torn down before any other tear down logic happens (because, presumably the instances are all created after all other setup occurs).

A Fixture instance can be used as a context manager. It is set up before entering the block of code it manages, and torn down upon exiting it.

Changed in version 3.2: Added support for del_ methods.

Changed in version 4.0: Changed to work with pytest instead of nosetests (with_fixtures, scope(), uses()).

Changed in version 4.0: Removed .run_fixture and .context.

Changed in version 4.0: Removed _del methods in favour of allowing new_ methods to yield, then tear down.

clear()

Clears all existing singleton objects

scenario

reahl.tofu.scenario

alias of reahl.tofu.fixture.Scenario

class reahl.tofu.fixture.Scenario(function)

A Scenario is a variation on a Fixture.

A Scenario is defined as a Fixture method which is decorated with @scenario. The Scenario method is run after setup of the Fixture, to provide some extra setup pertaining to that scenario only.

When a Fixture that contains more than one scenario is used with with_fixtures(), the test will be run once for every Scenario defined on the Fixture. Before each run of the Fixture, a new Fixture instance is set up, and only the current scenario method is called to provide the needed variation on the Fixture.

set_up

reahl.tofu.set_up

alias of reahl.tofu.fixture.SetUp

class reahl.tofu.fixture.SetUp(function)

Methods on a Fixture marked as @set_up are run when the Fixture is set up.

tear_down

reahl.tofu.tear_down

alias of reahl.tofu.fixture.TearDown

class reahl.tofu.fixture.TearDown(function)

Methods on a Fixture marked as @tear_down are run when the Fixture is torn down.

Integration with pytest

with_fixtures

reahl.tofu.with_fixtures

alias of reahl.tofu.pytestsupport.WithFixtureDecorator

class reahl.tofu.pytestsupport.WithFixtureDecorator(*fixture_classes)

A decorator for injecting Fixtures into pytest test method arguments.

This decorator takes a list of Fixture classes as arguments and ensures that the first declared positional arguments of the test_ function it decorates will be populated with instances of the corresponding Fixture classes when the test is run.

The names of these positional arguments do not matter.

If a Fixture in this list has scenarios, the test function will be run repeatedly–once for each scenario. If more than one Fixture in this list has scenarios, the test_ function will be repeated once for each combination of scenarios.

For example:

class MyFixture(Fixture):
    def new_string(self):
        return 'this is a test'

@with_fixture(MyFixture)
def test_this(my_fix)
    assert my_fix.string == 'this is a test'

The use of Fixture classes can me mixed with pytest.fixture functions. In such a case, the Fixture instances are passed to the first declared positional arguments of the test function, leaving the remainder of the arguments to be interpreted by pytest itself:

class MyFixture(Fixture):
    def new_string(self):
        return 'this is a test'

class MyOtherFixture(Fixture):
    def new_int(self):
        return 123

@pytest.fixture
def another_string():
    return 'another'

@with_fixture(MyFixture, MyOtherFixture)
def test_this(my_fix, other, another_string)
    assert my_fix.string == 'this is a test'
    assert other.int == 123
    assert another_string == 'another'

New in version 4.0.

uses

reahl.tofu.uses(**fixture_classes)

A decorator for making one Fixture use others.

The following will result in an instance of FixtureClass1 being instantiated every time a MyFixture is created. This instance will be available in MyFixture as its .name1 attribute:

@uses(name1=OtherFixture)
class MyFixture(Fixture):
    def some_method(self):
        assert isinstance(self.name1, OtherFixture)

New in version 4.0.

scope

reahl.tofu.scope(scope)

A decorator for setting the scope of a Fixture.

By default, all Fixtures are in ‘function’ scope, meaning they are created, set up, and torn down around each test function run. With @scope this default can be changed to ‘session’ scope. A session scoped Fixture is created and set up the first time it is entered as context manager, and torn down only once: when the test process exits.

If the Fixture contains multiple scenarios, a session scoped instance is created and set up for each scenario.

@scope('session')
class MyFixture(Fixture):
    pass

New in version 4.0.

Testing for exceptions

expected

reahl.tofu.expected(exception, test=None)

Returns a context manager that can be used to check that the code in the managed context does indeed raise the given exception.

Parameters
  • exception – The class of exception to expect

  • test – Either a function that takes a single argument or a regex. If test is a function, it will be called upon catching the expected exception, passing the exception instance as argument. A programmer can do more checks on the specific exception instance in this function, such as check its arguments. If test is a regex in a string, break if str(exception) does not match the regex.

Specifying test and regex_test are mutually exclusive.

For example, the following code will execute without letting a test break:

with expected(AssertionError):
    # some code here
    # .....
    # then at some point an exception is raised
    raise AssertionError()
    #.....

Changed in version 4.0: Changed to allow a regex in test keyword argument.

NoException

class reahl.tofu.NoException

A special exception class used with expected() to indicate that no exception is expected at all.

For example, the following code will break a test:

with expected(NoException):
    # some code here
    # .....
    # then at some point an exception is raised
    raise AssertionError()
    #.....

check_limitation

reahl.tofu.check_limitation(coded_version, msg)

Warns that a newer Python version is now used, which may have a fix for a limitation which had to be worked around previously.

Parameters
  • coded_version – The version of Python originally used to write the code being tested. The limitation is present in this version and the test will only break for newer versions than coded_version.

  • msg – The message to be shown if a newer Python version is used for running the code.

Temporary files and directories

file_with

reahl.tofu.file_with(name, contents, mode='w+')

Creates a file with the given name and contents. The file will be deleted automatically when it is garbage collected. The file is opened after creation, ready to be read.

Parameters
  • name – The full path name of the file to be created.

  • contents – The contents of the file. Must text unless binary mode was specified, in which case bytes should be used.

  • mode – The mode to open the file in, as per open() builtin.

temp_dir

reahl.tofu.temp_dir()

Creates an AutomaticallyDeletedDirectory.

temp_file_name

reahl.tofu.temp_file_name()

Returns a name that may be used for a temporary file that may be created and removed by a programmer.

temp_file_with

reahl.tofu.temp_file_with(contents, name=None, mode='w+')

Returns an opened, named temp file with contents as supplied. If name is supplied, the file is created inside a temporary directory.

Parameters
  • contents – The contents of the file. Must text unless binary mode was specified, in which case bytes should be used.

  • name – If given, the the name of the file (not including the file system path to it).

  • mode – The mode to open the file in, as per open() builtin.

AutomaticallyDeletedDirectory

class reahl.tofu.AutomaticallyDeletedDirectory(name)

A directory that is deleted upon being garbage collected.

Parameters

name – The full path name of the directory.

file_with(name, contents, mode='w+')

Returns a file inside this directory with the given name and contents.

temp_dir()

Returns a directory inside this directory.

sub_dir(name)

Returns a directory inside this directory with the given name.