After reading Organizing Test Suites with Test::Class, you're probably
and saying "that's a heck of a lot of work just for testing a class." If this
were all there is to it, you'd be perfectly justified in forgetting about Test::Class. However,
Test::Class
really shines when it comes to code re-use. Consider
writing a subclass of Person
named Person::Employee
.
I'll keep it simple by only providing an employee_number
method,
but you'll quickly understand the benefits.
package Person::Employee;
use Moose;
extends 'Person';
has employee_number => ( is => 'rw', isa => 'Int' );
1;
Here's its test class:
package Test::Person::Employee;
use Test::Most;
use base 'Test::Person';
sub class {'Person::Employee'}
sub employee_number : Tests(3) {
my $test = shift;
my $employee = $test->class->new;
can_ok $employee, 'employee_number';
ok !defined $employee->employee_number,
'... and employee_number should not start out defined';
$employee->employee_number(4);
is $employee->employee_number, 4,
'... but we should be able to set its value';
}
1;
Notice that instead of inheriting from Test::Class
, the
test inherits from Test::Person
, just like
Person::Employee
class inherited from Person
.
Also, this overrides the class
method to ensure that tests
know which class they're using.
Remember to add Test::Person::Employee
to
t/run.t:
#!/usr/bin/env perl -T
use lib 't/tests';
use Test::Person;
use Test::Person::Employee;
Test::Class->runtests;
And when we run it t/run.t
:
All tests successful.
Files=1, Tests=31, 1 wallclock secs ( 0.25 cusr + 0.06 csys = 0.31 CPU)
Whoa! Wait a minute. This new test class only had three tests. The previous run ran with 14, so how come the report says it ran 31?
Test::Person::Employee
inherited the tests from
Test::Person
. The 14 original tests plus the 14 inherited
tests and the 3 added tests add up to 31 tests! These aren't frivolous
tests, either. Look at the new test's output:
# Test::Person::Employee->constructor
ok 16 - Person::Employee->can('new')
ok 17 - ... and the constructor should succeed
ok 18 - ... and the object it returns isa Person::Employee
#
# Test::Person::Employee->employee_number
ok 19 - Person::Employee->can('employee_number')
ok 20 - ... and employee_number should not start out defined
ok 21 - ... but we should be able to set its value
#
# Test::Person::Employee->first_name
ok 22 - Person::Employee->can('first_name')
ok 23 - ... and first_name should start out undefined
ok 24 - ... and setting its value should succeed
#
# Test::Person::Employee->full_name
ok 25 - Person::Employee->can('full_name')
ok 26 - ... and full_name() should croak() if the either name is not set
ok 27 - ... and full_name() should croak() if the either name is not set
ok 28 - ... and setting its value should succeed
#
# Test::Person::Employee->last_name
ok 29 - Person::Employee->can('last_name')
ok 30 - ... and last_name should start out undefined
ok 31 - ... and setting its value should succeed
By not explicitly hard-coding the class name in the tests and because
Test::Person::Employee
had overridden the class
method, these new tests run against instances of
Person::Employee
, not Person
. This demonstrates
that subclassing did not break any of the inherited behavior! However, if
you do need to alter the behavior of one of those methods, as you might
expect with object-oriented code, all you need to do is override the
corresponding test method. For example, what if employees must have their
full names listed in the format "last name, first name"?
sub full_name {
my $self = shift;
unless ( $self->first_name && $self->last_name ) {
Carp::croak("Both first and last names must be set");
}
return $self->last_name . ', ' . $self->first_name;
}
The appropriate test method in Test::Person::Employee
might
look like:
sub full_name : Tests(no_plan) {
my $test = shift;
my $person = $test->class->new;
can_ok $person, 'full_name';
throws_ok { $person->full_name }
qr/^Both first and last names must be set/,
'... and full_name() should croak() if the either name is not set';
$person->first_name('John');
throws_ok { $person->full_name }
qr/^Both first and last names must be set/,
'... and full_name() should croak() if the either name is not set';
$person->last_name('Public');
is $person->full_name, 'Public, John',
'... and setting its value should succeed';
}
Make those changes and all tests will pass.
Test::Person::Employee
will call its own
full_name
test method and not that of its parent class.
Refactoring test classes
There's a lot of duplication in the full_name
test which
you should factor out into common code. The well-known (if
poorly-practiced) aphorism that test code is just code is even more true
when Test::Class
. Well-factored tests are easier to
understand, to maintain, and to modify than poorly-factored tests.
Refactoring with methods
One approach to reduce duplication in Test::Person
class
might be to create helper methods:
sub full_name : Tests(no_plan)
my $test = shift;
$test->_full_name_validation;
my $person = $test->class->new(
first_name => 'John',
last_name => 'Public',
);
is $person->full_name, 'John Public',
'The name of a person should render correctly';
}
sub _full_name_validation {
my ( $test, $person ) = @_;
my $person = $test->class->new;
can_ok $person, 'full_name';
throws_ok { $person->full_name }
qr/^Both first and last names must be set/,
'... and full_name() should croak() if the either name is not set';
$person->first_name('John');
throws_ok { $person->full_name }
qr/^Both first and last names must be set/,
'... and full_name() should croak() if the either name is not set';
}
And in Test::Person::Employee
:
sub full_name : Tests(no_plan)
my $test = shift;
$test->_full_name_validation;
my $person = $test->class->new(
first_name => 'Mary',
last_name => 'Jones',
);
is $person->full_name, 'Jones, Mary',
'The employee name should render correctly';
}
Just like with any other OO code, subclasses inherit and can override
the _full_name_validation
method.
Refactoring with fixtures
When writing test classes, the startup
and
shutdown
methods are very handy, but those run only at the
beginning and end of your test class. Sometimes you need code to run before
the beginning and end of every test method. In the Person
examples, many of the test methods contained this line:
my $person = $test->class->new;
You really may not want to duplicate that every time, so you can use what's known as a fixture. A fixture is "fixed state" for your tests to run against. These allow you to remove duplicate setup code from your tests and to have a controlled environment. You might write:
sub setup : Tests(setup) {
my $test = shift;
my $class = $test->class;
$test->{person} = $class->new;
}
If you want to start with a known set of data, you could write:
sub setup : Tests(setup) {
my $test = shift;
my $class = $test->class;
$test->{person} = $class->new(
first_name => 'John',
last_name => 'Public',
);
}
Now all of your test methods can simply use $test->{person}
(you can even make that a method if you prefer) to access a new instance of
the class you're testing without having to duplicate that code.
The corresponding teardown
method is useful if you need to
clean up on a per test basis. This can be useful if you run tests against a
database.
Next time, I'll discuss how to manage test classes with Test::Class.
Ovid,
Thanks for these two articles. I've just started learning about TDD and they are just what I needed to grok the situation so I can start writing tests effectively and quickly.
--[Lance]