Test Suite Organization

I've written about this before, but now I want to show some slides from my new testing class because I think it's easier to show test suite organization rather than describe it.

Years ago I wrote HTML::TokeParser::Simple, an OO interface to HTML::TokeParser. The latter is a great module, but you are typically working with a bunch of array references like this:

["S",  $tag, $attr, $attrseq, $text]
["E",  $tag, $text]
["T",  $text, $is_data]
["C",  $text]
["D",  $text]
["PI", $token0, $text]

That can be confusing and you often have to write code that is hard to immediately understand. For example, here's the code to strip all comments from an HTML document:

while (my $token = $p->get_token) {
    next if 'C' eq $token->[0];
    say $token->[-1];
}

That small snippet isn't too bad, but when you're doing complex work with HTML documents, it can become hairy. Here's how HTML::TokeParser::Simple works:

while (my $token = $p->get_token) {
    next if $token->is_comment;
    say $token->as_is; # not brilliant, but there you go
}

The test suite, however, is a bit of a problem.

It looks sort of like this:

I then ask students "where are the tests for HTML::TokeParser::Simple::Token::ProcessInstruction? (See process instruction if you're curious what that means). Students reading the slides might guess that the tests are in t/get_token.t. That's a reasonable guess, but the follow up question is "are you sure?"

No, they're not sure.

For a small module like this, the test suite isn't too terrible, but when you're working on a large application for a company and you have hundreds of packages, organizing tests by functionality can make it a nightmare to guess where the tests should go. More than once I've seen myself or another developer losing valuable time hunting for tests which may or may not exist. For larger test suites like that, I often find tests for various bits of functionality scattered hither and yon, and often duplicated, too.

Then I put up the following slide:

Once again, "where are the tests for HTML::TokeParser::Simple::Token::ProcessInstruction?

Ah! They're not there! You can even write a test to ensure that every *.pm file has an associated test file. With a test suite organized like this, you can instantly know where any and all tests go:

I sometimes hear people complain "but that will lead to larger, monolithic test files." Yes, this pushs up the size of the test files, but switching to one of the Test::Class::* modules can easily reign in the size and complexity of your test suite, particularly if you're working with OO code and inheritance.

Interestingly, I've also found that every time I need to reorganize the test suite to the above format, it's generally only a few hours of work (it's never taken me more than a day). I can quickly write a script to generate that test skeleton and start switching over test files one by one. As a bonus, adopting this strategy makes it trivial to program your editor to automatically switch back and forth between code and tests, further saving you time.

The reason the tests are in the t/tests directory is because I like to put test helper modules in t/lib. The top level t directory has various odd tests such as t/00-load.t, t/migration.t and so on.

If you want to separate out your tests by functionality, such as Web tests, I sometimes have a t/web directory and the filenames map to URLs rather than packages.

I might add that I adopt this strategy almost at the beginning of the class to get students used to it quickly, rather than using the old-style t/*.t tests and forcing them to switch over later.

2 Comments

This sort of thing is one of the reasons I wrote Test::Manifest. I could specify a list of any files I like, including those in deep subdirectories, to run as tests. Not only that, I didn't have to order them lexicographically so I could make names that track what they test.

I don't particularly like my solution, but it was the easiest way to get what I wanted and to work within the ExtUtils::MakeMaker system.

typo: "rein in the size", not "reign in the size"

...but I like the idea.

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/