Embedding tests in a script

schwern on 2008-04-23T23:59:48

From time to time I'll work on a code base that's basically a pile of individual scripts. The process of converting it to a modularized system can take some time, technically as well as socially. Meanwhile, I have to get work done. And for me getting work done requires writing tests.

But if it's a pile of scripts, where do you put them? And with no build structure, how do you run them? Rather than having to decide between using a single file OR writing tests, I decided to embed the tests in the scripts themselves. Observe.

sub selftest {
    my @test_functions = get_test_functions();

for my $function (sort { lc $a cmp lc $b } @test_functions) { no strict 'refs'; print "# Running $function\n"; &{$function}; } }

sub get_test_functions { my $package = shift || __PACKAGE__;

# Load the test functions after __END__ eval join '', ;

no strict 'refs';

return # Select only those which are subroutines grep { defined &{$_} }

# Find the ones named test_* grep /^test_/,

# Get all the symbols in the package keys %{$package."::"}; }

use Getopt::Long;

sub main { my %options; GetOptions( \%options, "test", );

if( $options{test} ) { selftest(); exit; }

... rest of the code here ... }

main();

__END__ # These tests will be compiled and run when --test is given

use strict; use warnings;

use Test::More 'no_plan';

sub test_the_tests { pass("The tests run!"); }


Giving a --test compiles the __END__ code (in selftest()), finds all the test_* functions, runs them and exits.

By embedding the tests into the scripts you can introduce unit testing to single-file scripters without having to simultaneously introduce the concept of a multi-file project. By putting the tests after the __END__ block nobody can make the excuse that your test functions are wasting memory in production.

I'm sure I'm not the first to come up with this, but I don't know that I've seen it modularized. So before I go and do that, is this already on CPAN?


Similar...

sigzero on 2008-04-24T01:49:24

Yours is neat as well.

http://search.cpan.org/dist/Test-Inline/lib/Test/Inline.pm

Re:Similar...

schwern on 2008-04-24T19:29:42

What an amazing coincidence. I still need to get POD example testing back into Test::Inline 2.

Test::Inline and selftest are similar in that they both follow the idea of embedding the tests into the code being tested. Test::Inline focuses on putting the test as close to the code being tested as possible, but it requires scaffolding to run those tests. selftest focuses on being able to deliver a single, self-contained package.

I think I'll release it as Test::Yourself. What do you think?

Re:Similar...

Alias on 2008-04-25T01:47:53

Test::Inline (both Schwern's original Pod::Tests and my replacement) aren't really suitable for this situation, as he's applying it to standalone scripts rather than distributions.