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
shutdown
setup
teardown
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 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.