In this series, I've explained how to use Test::Class in Perl, how to reuse Test::Class tests, and how to simplify Test::Class tests, and how to manage data dependencies and fixtures with Test::Class tests. If you've followed along -- and if you've written your own tests with Test::Class, you're on your way to becoming a testing expert. Now it's time to discuss some ancillary issues you may encounter.
Performance
With Test::Class::Load, you can run all of your test class tests in one process:
use Test::Class::Load qw<path/to/tests>;
That loads the tests and all modules you're testing once. This can be
a huge performance boost if you're loading "heavy" modules such as Catalyst or DBIx::Class. However, be
aware that you're now loading all classes in a single process; there are
potential drawbacks here. For example, if one of your classes alters a
singleton or global variable that another class depends on, you may get
unexpected results. Also, many classes load modules which globally alter Perl's
behavior. Grep through your CPAN modules for UNIVERSAL::
or
CORE::GLOBAL::
to see just how many classes do this.
Global state changes can introduce difficult-to-diagnose bugs. You will
have to decide for yourself whether the benefits of
Test::Class
outweigh these drawbacks. My experience is that
these bugs are usually very painful to resolve, but in finding them, I
often find intermittant problems in my code bases that I could not have
found any other way. For me, Test::Class
offers many benefits,
despite occasional frustrations.
People who prefer not to run all of their code in a single process often create separate "driver" tests:
#!/usr/bin/env perl -T
use Test::Person;
Test::Class->runtests;
... and:
#!/usr/bin/env perl -T
use Test::Person::Employee;
Test::Class->runtests;
Remember to omit the call to runtests
if you've included
this in your base class INIT
.
Making Your Classes Behave Like xUnit
Classes
In xUnit style tests, this is an entire test:
sub first_name : Tests(tests => 3) {
my $test = shift;
my $person = $test->class->new;
can_ok $person, 'first_name';
ok !defined $person->first_name,
'... and first_name should start out undefined';
$person->first_name('John');
is $person->first_name, 'John', '... and setting its value should succeed';
}
The TAP world considers this as three tests, but xUnit regards these three
assertions as validations of a single feature, and thus one test. TAP-based
tests have a long way to go before working for xUnit users, but there's one
thing we can do. Suppose that you have a test with 30 asserts. The fourth
assert fails. Many xUnit programmers argue that once an assert fails, the rest
of the information in the test is unreliable. In that case, the test driver
often halts. Regardless of whether you agree (I hate that JUnit requires the
test method to stop), you can get this behavior with Test::Class
.
Use Test::Most instead
of Test::More
and put this in your test base class:
BEGIN { $ENV{DIE_ON_FAIL} = 1 }
Because each test method in Test::Class is wrapped in an eval, that test
method will stop running, the appropriate teardown
method (if
any) will execute and the tests will resume with the next test method.
I'm not a huge fan of this technique, but your mileage may vary.
Conclusion
While many projects work just fine using simple Test::More
programs, larger projects can wind up with scalability problems.
Test::Class
gives you better opportunities for managing your
tests, refactoring common code, and having your test code better mirror
your production code.
Here's a quick summary of tips in this series:
- Name your test classes consistently after the classes they're testing.
- When possible, do the same for your test methods.
- Don't use a constructor test named
new
. - Create your own
Test::Class
base class. - Abstract the the name of the class you're testing into a
class
method in your base class. - Name test control methods after their attribute.
- Decide case-by-case whether to call a control method's parent method.
- Don't put tests in your test control methods.
Acknowledgments
Thanks to Adrian Howard for creating the Test::Class
module
and providing me with tips in making it easier to use. Also, David Wheeler
provided some useful comments, but that was on a first draft written years
ago. I wonder if he remembers?
The ability to run all classes in a single process is great, but needing to create one driver per class is a pain for individual testing.
One slightly hacky workaround is to place this:
at the bottom of each of your Test::Class subclasses that contain actual tests. Then, you can just run "prove" on your testing .pm files to test individually, without the need for a separate driver.
That boilerplate isn't necessary if you use the recommendation from the previous article
http://www.modernperlbooks.com/mt/2009/03/making-your-testing-life-easier.html
with this INIT block in your base test class (only):
INIT { Test::Class->runtests }
Then you can run perl or prove on any module file to run just the tests for that (sub)class.