Mutant has asked for the wisdom of the Perl Monks concerning the following question:

We have a lot of unit test code that doesn't really make use of any nicities such as Test::Class, Test::MockObject or even Test::More (it's mostly based on Test). As a result, there's a lot of messy, verbose and duplicated code. I've decided to make it my mission to get this tidied up, which is daunting, but I think will help my sanity long term.

Adapting what we have to Test::Class should be reasonably easy, it's just a matter of doing a large scale re-factor. While I haven't nailed down the exact details (which will probably require some input from my colleagues), I'm thinking we'll organise the test classes by module, e.g. Foo::Bar will have a corresponding Foo::Bar::Test

My problem is, we have lots of test code like this:

my @Tests = ( { 'desc' => 'failure', 'params' => {}, 'result' => "some error msg", }, { 'desc' => 'success', 'params' => {bar=>1, baz=2}, 'result' => "ok", }, ); foreach my $test (@Tests) { ok( sub { Foo->new($test->{params}); 'ok'; }; 'ok', "test new() " . ( exists( $test->{'desc'} ) ? " - $test->{'desc' +}" : '' ) ); }

Obviously, this is a fairly trivial example - more things can end up in the foreach loop to set up things before the method we're testing is called. My problem with this way of doing this is it makes the tests quite hard to read. I have to look at things carefully to figure out what's going on.

However, I'm not really sure of a good alternative. The setup methods in Test::Class aren't really much use, because it's only relevant to this particular method. I could break the test classes down into one for each method in a class, but that might be a bit too much fragmentation.

So does anyone else have a good solution for organising these types of tests?

Replies are listed 'Best First'.
Re: Do you use loops in your unit tests?
by kyle (Abbot) on Jan 04, 2008 at 16:56 UTC
      Thanks, yeah, that is a lot more readable. I guess when you've been looking at code like this in hundreds of tests you can get a mental block on how to improve it (well, I can anyway).
Re: Do you use loops in your unit tests?
by xdg (Monsignor) on Jan 04, 2008 at 17:41 UTC

    I use this style of testing quite often. (C.f. Re^2: Wanted, more simple tutorials on testing) While it's not the most elegant, I find it quick to get started this way before I've really decided how the tests will evolve and what the common setup/teardown will be and then it's relatively easy to extend and refactor as needed.

    I often put one or more *.pm files in the "t" directory with either helper functions or to to encapsulate repetitive tests. This is where Test::More with Test::Builder and $Test::Builder::Level are quite helpful. Essentially, you can create custom test modules on the fly as needed to refactor cut-and-paste tests from multiple parts of your *.t files.

    # foo.t use strict; use warnings; use Test::More; # don't put a plan here use t::Helper qw/multi_test/; # i.e. t/Helper.pm my @cases = ( { label => "describe this case", input => "data to operate on", output => "expected result", }, # ... repeat as needed ... ); # set plan after populating @cases my $tests_per_case = 2; plan tests => 1 + $tests_per_case * @cases; # start testing here require_ok( "Some::Object" ); for my $case ( @cases ) { my $obj = Some::Object->new; multi_test( $obj, $case ); }
    # t/Helper.pm use strict; use warnings; use base 'Exporter'; our @EXPORT = qw/multi_test/; sub multi_test { my ($obj, $case) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; is( $obj->wibble( $case->{input} ), $case->{output}, "wibble: $case->{label}" ); ok( $obj->wobble, "wobble: $case->{label} ); } 1;

    You can see a range of examples in the *.t files in some of my CPAN modules:

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Do you use loops in your unit tests?
by perrin (Chancellor) on Jan 04, 2008 at 17:19 UTC
    I don't understand precisely what you're objecting to about this code example, but I have found it's impossible to write large scale tests with helper methods. I use subs in my tests like I would in any other program to break down functionality into small parts. The trick with tests is that you may want to use some very basic Test::Builder in order to make sure you get a useful error message when the tests fail, e.g. print out what the relevant test data was, not just the line number.
Re: Do you use loops in your unit tests?
by dragonchild (Archbishop) on Jan 06, 2008 at 03:02 UTC
    Of course! Take a look at the tests for Excel::Template or DBM::Deep. It's very common to see a t/common.pm or t/lib/common.pm in CPAN distros or work distros. Even if it's just a bunch of functions that provide useful encapsulation around File::Temp and similar things.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?