Test::Class Recipe: Safe Fixture Methods

Ovid on 2008-04-23T09:51:34

I have written an article on Test::Class and really need to get around to finalizing and publishing it, but in the meantime, I think some Test::Class "recipes" are in order.

Problem: Your child's setup method is firing before the parent's setup method.

Solution: Give them the same name and use SUPER.

Explanation: In Test::Class, you have methods I think of as "fixture" methods, though that's not quite true and a better name is welcome. These methods are for:

startup: executes before the test class runs.
Example: create the database and connect.
shutdown: executes when the test class ends.
Example: drop the database and disconnect.
setup: executes before each test method.
Example: add test data.
teardown: executes after each test method.
Example: clear test data.

The problem is when you have a subclass. Since test methods run in alphabetic order, every class with "fixture" methods must know the case of complementary fixture methods in parent classes or child classes in order to ensure that they get fired in the right order. If you have this, your code will fail.

sub Parent::connect_to_db : Tests(startup) {
    my $test = shift;
    $test->dbh(connect());
}

# inherits from Parent
sub Child::begin_transaction : Tests(startup) {
    my $test = shift;
 
    # This fails because 'b' is run before 'c' (first letter of
    # method names) and dbh() is not yet defined
    my $dbh  = $test->dbh;
    ...
}

We can avoid this by naming all of these methods after the phase in which they get handled.

sub Parent::startup : Tests(startup) {
    my $test = shift;
    $test->dbh(connect());
}

# inherits from Parent
sub Child::startup : Tests(startup) {
    my $test = shift;
    $test->SUPER::startup; 
    my $dbh  = $test->dbh;
    ...
}

In startup and setup, the SUPER call should happen at the start of the method. In teardown and shutdown, the SUPER call should happen at the end of the method.


Typo?

ChrisDolan on 2008-04-24T01:13:41

Ovid wrote:

We can avoid this by naming all of these methods after the phase in which they get handled.

        sub Parent::setup : Tests(startup) {


I think you meant to name that method (and the subclass method) startup

Re:Typo?

Ovid on 2008-04-24T22:12:07

Yes I did. Nice catch (and fixed) :)