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:
startupshutdownsetupteardown
This method runs once for each class, before any tests run.
This method runs once for each class, after all tests have run.
This method runs before each test method.
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 dies
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.