Working with Test::Class Test Suites

| 2 Comments

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?

2 Comments

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:

__PACKAGE__->runtests unless caller();

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.

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by Ovid published on March 20, 2009 1:52 PM.

Using Test Control Methods with Test::Class was the previous entry in this blog.

Feedback-Directed Development is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?