After absorbing the information in Organizing Test Suites with Test::Class and Reusing Test Code with Test::Class, you're probably beginning to understand how Test::Class can make managing large codebases easier. If you've worked with test cases before, you've likely realized that test code is still code. Well-organized test code is easier to work with than poorly organized test code.

Auto-discovering your test classes

There's too much repetitive boilerplate in these tests. We can make them easier. The first problem is the helper script, t/run.t:

#!/usr/bin/env perl -T use lib 't/tests'; use Test::Person; use Test::Person::Employee; Test::Class->runtests;

Right now, this doesn't look so bad, but as you start to add more classes, this gets to be unwieldy. What if you forget to add a test class? Your class might be broken, but if the test class does not run, how will you know? Autodiscovering test classes helps:

#!/usr/bin/env perl -T use Test::Class::Load qw<t/tests>; Test::Class->runtests;

Tell Test::Class::Load (bundled with Test::Class ) which directories your test classes are in and it will find them for you. It does this by loading attempting to load all files with a .pm extension, so keep any helper test modules (which are not Test::Class tests) in a separate directory.

Using a common base class

Another useful technique of programming in general is to factor out common code. I've demonstrated this already, but there's room for improvement. Both test classes have a method for returning the name of the class being tested. It's possible to compute the name of this class, so why not push this into a base class? Add this to t/tests/My/Test/Class.pm:

package My::Test::Class; use Test::Most; use base qw<Test::Class Class::Data::Inheritable>; BEGIN { __PACKAGE__->mk_classdata('class'); } sub startup : Tests( startup => 1 ) { my $test = shift; ( my $class = ref $test ) =~ s/^Test:://; return ok 1, "$class loaded" if $class eq __PACKAGE__; use_ok $class or die; $test->class($class); } 1;

In Person::Employee , delete the class method. In Person , delete the class and startup methods, and inherit from My::Test::Class instead of Test::Class . Now, class will always return the current class under testing. The new Test::Person class looks like:

package Test::Person; use Test::Most; use base 'My::Test::Class'; sub constructor : Tests(3) { my $test = shift; my $class = $test->class; can_ok $class, 'new'; ok my $person = $class->new, '... and the constructor should succeed'; isa_ok $person, $class, '... and the object it returns'; } sub first_name : 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'; } sub last_name : Tests(3) { my $test = shift; my $person = $test->class->new; can_ok $person, 'last_name'; ok !defined $person->last_name, '... and last_name should start out undefined'; $person->last_name('Public'); is $person->last_name, 'Public', '... and setting its value should succeed'; } sub full_name : Tests(4) { my $test = shift; $test->_full_name_validation; my $person = $test->class->new( first_name => 'John', last_name => 'Public', ); is $person->full_name, 'John Public', '... and setting its value should succeed'; } 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'; } 1;

The test results for Test::Person::Employee are:

All tests successful. Files=1, Tests=32, 1 wallclock secs ( 0.33 cusr + 0.08 csys = 0.41 CPU)

There's an extra test, due to the ok 1 found in the My::Test::Class::startup method. It gets called an extra time for the loading of My::Test::Class .

Tip: If you must load your at BEGIN time, override this startup method in your test class -- but be sure to provide a class method.

Run individual test classes

When I develop tests, I hate to leave my editor merely to run tests from the command line. To avoid this, I a mapping in my .vimrc file similar to:

noremap ,t :!prove --merge -lv %<CR>

When writing tests, I hit ,t and my test runs. However, doing this in a test class doesn't work. The class gets loaded, but the tests do not run. I could add a new mapping:

noremap ,T :!prove -lv --merge t/run.t<CR>

... but this runs all of my test classes. If I have several hundred tests, I don't want to hunt back through all of the test output to see which tests failed. Instead, I want to run a single test class. I altered my mapping to include the path to my test classes.

noremap ,t :!prove -lv --merge -It/tests %<CR>

I also removed the Test::Class->runtests line from t/run.t (or else I'll have my tests run twice if I run the full test suite). Because I use a common base class, I added a line to My::Test::Class :

INIT { Test::Class->runtests }

Regardless of whether I'm in a standard Test::Most test program or one of my new test classes, I can type ,t and run only the tests in the file I'm editing.

If you run the tests for Test::Person::Employee , you'll see the full run of 32 tests because Test::Class will run the tests for the current class and all classes from which it inherits. If you run the tests for Test::Person , you'll only see 15 tests run -- the desired behavior.

If you prefer Emacs, add this to your ~/.emacs file:

(eval-after-load "cperl-mode" '(add-hook 'cperl-mode-hook (lambda () (local-set-key "\C-ct" 'cperl-prove)))) (defun cperl-prove () "Run the current test." (interactive) (shell-command (concat "prove -lv --merge -It/tests " (shell-quote_argument (buffer-file-name)))))

That will bind this to C-c t and you can pretend that you're as cool as Vim users (just kidding! Stop the hate mail already).

Next time, learn to use test control methods with Test::Class.