As the Testing section of the Modern Perl book tries to explain, Perl tests are just Perl code. Sure, the libraries built on top of Test::Builder extend your testing toolbox by providing new functions to help you write tests, but tests are just code.
The first implication of that statement is that, if you know how to write code, you can write tests.
The second implication of that statement—one which takes longer to realize—is that test code has maintenance costs just like code code does.
I like using Test::Class and Test::Routine when they make sense, but some tests are easier to write when they start with a little less ceremony. (I also like to refine and reorganize my tests when better organization suggests itself.) In those cases, I start with something simpler:
#!/usr/bin/env perl
use Modern::Perl '2013';
use Test::More;
use lib 't/lib';
exit main( @ARGV );
sub main {
my @args = @_;
test_this();
test_that();
test_the_other();
done_testing;
return 0;
}
Every set of test assertions I can collect into a named group is a function.
(You can even use Test::More
subtests if you like.) When a
function gets too long—when I can come up with a new name for a subset of
the behavior under test—it's time for a new function.
(I write exit main( @ARGV );
as the first non-pragma line of
the program so that there are no order-of-initialization concerns from Perl's
compile-time/run-time distinctions. If that means nothing to you, carry on;
it's a subtle thing that almost never causes problems.)
Grouping everything under functions also has the benefit of reminding other developers that it's okay to write more functions. For example, if every test for every action in a controller ought to verify access permissions, it's easy to write a helper function to set up an environment with and without access permissions to test both ways. (Some of these test functions get promoted to project-specific test libraries.)
I suspect that the discipline of writing test files as if they were code, not just scripts, is that it provides a subtle pressure to continue to write tests as if it were code, with all of the discipline and factoring and maintenance work that implies. It's easier to take something a little messy and make it a lot messier, but for me at least it's nice to start with something clean and leave it at least that clean. (This is probably why dishes pile up in my office when the dishwasher needs to be emptied.)
Please note that these test names do not necessarily correspond to
individual methods or functional units within the code under test. That is,
just because I might have a method called
analyze_country_performance_metrics()
, I don't have a single test
function to test that method. I might have several. I might have none (if other
code tests it adequately). Each test function tests a single, coherent,
nameable feature, like "editing a user account with invalid data" or "exporting
person search to CSV".
This is subtle, but it's important. Think in terms of features you can
discuss with end users. (Perhaps that means I find Test::Class
and
its friends more useful for unit-style testing.) This helps you navigate your
way through the test suite and your issue tracker by giving everyone canonical
names to describe tests.
A little structure is valuable. It's easy to set up and to maintain. Yes, there's a little bit of duplication in calling functions and declaring them, but the benefit of navigation and nomenclature outweigh that many times over. The specific structure of your test files doesn't matter as much as that structure exists and you take advantage of it.
(It also lets you run test functions individually as you're fixing bugs or adding features, but the mechanics of that are the subject of another post.)