Handling Startup/Setup/Teardown/Shutdown Methods

When you understand Organizing Test Suites with Test::Class, Reusing Test Code with Test::Class, and Removing Boilerplate Testing Code, you'll likely to discover that you need to have special code run at the start and end of a class and at the start and end of every test method. This code might connect to databases, delete temp files, or set up test fixtures.

Test::Class provides four test control methods to help:

startup

This method runs once for each class, before any tests run. shutdown

This method runs once for each class, after all tests have run. setup

This method runs before each test method. teardown This method runs after each test method.

startup and shutdown

One common function for the startup and shutdown methods is to set up and tear down a database:

package Tests::My::Resultset::Customer; use base 'My::Test::Class'; sub startup : Tests(startup) { my $test = shift; $test->_connect_to_database; } sub shutdown : Tests(shutdown) { my $test = shift; $test->_disconnect_from_database; } ...

When the test class loads, the first code which Test::Class runs is startup . At the end of the test, Test::Class calls the shutdown method is called and we disconnect from the database. Note that if the startup method has any tests and one fails, or if it throws any exception, the rest of the tests will not run. Any tests for parent classes will still run.

sub startup : Tests(startup) { ok 0; # the test class will abort here }

If this occurs, the shutdown method will not be called.

setup and teardown

It can also be useful to run code before and after every test method. Here's how:

sub setup : Tests(setup) { my $test = shift; $test->_start_db_transaction; } sub check_priviledges : Tests(no_plan) { my $test = shift; $test->_load_priviledge_fixture; ... } sub teardown : Tests(teardown) { my $test = shift; $test->_rollback_db_transaction; }

This code starts a database transaction before every test method. The check_priviledges method loads its own test fixture and the teardown method rolls back the transaction, ensuring that the next test will have a pristine database. Note that if the setup method fails a test, the teardown method will still be called. This is different behavior for the startup method because Test::Class moves on to the next test and assumes you still want to continue.

Overriding test control methods

Users new to Test::Class often find that they run more test control methods than they expected or their test control methods run in an order they did not expect.

Controlling order of execution

Suppose that your test base class contains:

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

A test subclass contains:

sub assert_db : Tests(startup => 1) { my $test = shift; ok $test->_is_connected_to_db, 'We still have a database connection'; }

That will probably fail and your tests will not run. Why? Test::Class runs tests in alphabetical order in a test class. Because it includes inherited tests in your test class, you've inherited connect_to_db . As that sorts after assert_db , it runs after assert_db . Thus, you're asserting your database connection before you've connected.

The problem is tightly-coupled methods which rely on execution order. The fix is simple. Rename both startup methods to startup and have the child class call the super class method:

sub startup : Tests(startup) { my $test = shift; $test->SUPER::startup; die unless $test->_is_connected_to_db, 'We still have a database connection'; }

This works because Test::Class knows you've overridden the method and you can call it manually.

Warning: Note that the startup method now die s rather than running a test. Test::Class has no way of knowing if you're really going to call the super class. As a result, it has no way of knowing the real test count. The die halts the startup method.

Tip: for reasons mentioned above, don't put tests in your test control methods.

Controlling what gets executed

Suppose that you've a web page which provides additional features to authenticated users. You might test it with:

sub unauthenticated_startup : Test(startup) { my $test = shift; $test->_connect_as_unauthenticated; }

In your "authenticated" subclass, you may have:

sub authenticated_startup : Test(startup) { my $test = shift; $test->_connect_as_authenticated; }

Again, your tests will probably fail because authenticated_startup will run before unauthenticated_startup , and you have probably connected as the unauthenticated user in your "authenticated" subclass. However, this time you probably don't even need unauthenticated_startup to run. The solution is again to give the tests the same name without calling the parent's method:

sub startup : Test(startup) { my $test = shift; $test->_connect_as_authenticated; }

Note that this control method does not run tests. If the connection fails, throw an exception.

The next and final article in this series explains how to manage test suites with Test::Class.