NAME

My::Tests::Below - invoke a test suite at the end of a module.

SYNOPSIS

package MyPackage;

<the text of the package goes here>

require My::Tests::Below unless caller();

1;

__END__

use MyPackage;


# And there you go with your test suite

DESCRIPTION

DOMQ is a guy who releases CPAN packages from time to time - you are probably frobbing into one of them right now.

This package is a helper that supports my coding style for unit tests so as to facilitate relasing my code to the world.

How it works

The test code is written in perlmodlib style, that is, at the bottom of the Perl module to test, after an __END__ marker. This way of organizing test code is not unlike Test::Inline, by Adam Kennedy et al, in that it keeps code, documentation and tests in the same place, encouraging developers to modify all three at once.

Invoking require My::Tests::Below from anywhere (the idiomatic form is shown in "SYNOPSIS") results in the block of code after the __END__ marker being run at once. Due to the way this construct abuses the Perl module mechanism, My::Tests::Below cannot be require()d or use()d for any other purpose, hence the funny name.

Why not use Test::Inline then?

Well, for a variety of reasons:

  • modules written with tests at the end syntax-highlight almost perfectly under Emacs :-), which is far from being the case for tests written in the POD

  • removing the My::Tests::Below altogether from the installed version of a package is straightforward and does not alter line numbering. (See My::Module::Build)

  • no pre-processing step (e.g. inline2test) and no temporary file creation is required with My::Tests::Below. This goes a long ways towards shortening the debugging cycle (no need to re-run "./Build code" nor "make" each time)

  • Test::Inline has a lot of dependencies, and using it would cause the installation of small modules to become unduly burdensome.

Comfort features

Unlike the eval form recommended in perlmodlib, My::Tests::Below provides a couple of comfort features that help making the system smooth to use for tests.

Support for code and data snippets in the POD

A mechanism similar to the now-deprecated Pod::Tests is proposed to test documented examples such as code fragments in the SYNOPSIS. See "CLASS METHODS" below.

Line counting for the debugger

You can step through the test using a GUI debugger (e.g. perldb in Emacs) because the line numbers are appropriately translated.

Tests always start in package main

The perlmodlib idiomatics puts you either in main or in the package where the eval was called from, depending on the version of Perl.

Tested package is available for "use"

As shown in "SYNOPSIS", one can invoke "use MyPackage;" at the top of the test suite and this will not cause the package under test to be reloaded from the filesystem. The import() semantics of MyPackage, if any, will work as normal.

%ENV is standardized

When running under require My::Tests::Below, %ENV is reset to a sane value to avoid freaky side effects when eg the locales are weird and this influences some shell tool fired up by the test suite. The original contents of %ENV is stashed away in %main::ENVorig in case it is actually needed.

CLASS METHODS

tempdir()

This class method returns the path of a temporary test directory created using "tempdir" in File::Temp. This directory is set to be destroyed when the test finishes, except if the DEBUG environment variable is set. This class method is idempotent: calling it several times in a row always returns the same directory.

pod_data_snippet($snippetname)

This class method allows the test code to grab an appropriately marked section of the POD in the class being tested, for various test-oriented purposes (such as eval'ing it, storing it in a configuration file, etc.). The return value has the same number of lines as the original text in the source file, but it is ragged to the left by suppressing a constant number of space characters at the beginning of each line.

For example, consider the following module:

#!/usr/bin/perl -w

package Zoinx;
use strict;

=head1 NAME

Zoinx!

=head1 SYNOPSIS

=for My::Tests::Below "create-zoinx" begin

my $zoinx = new Zoinx;

=for My::Tests::Below "create-zoinx" end

=cut

package Zoinx;

sub new {
    bless {}, "Zoinx";
}

require My::Tests::Below unless caller;
__END__

then My::Tests::Below->pod_data_snippet("create-zoinx") would return "\nmy $zoinx = new Zoinx;\n\n".

The syntax of the =for My::Tests::Below POD markup lines obeys the following rules:

  • the first token after My::Tests::Below is a double-quoted string that contains the unique label of the POD snippet (passed as the first argument to pod_data_snippet());

  • the second token is either begin and end, which denote the start and end of the snippet as shown above. Nesting is forbidden (for now).

pod_code_snippet($snippetname)

Works like "pod_data_snippet", except that an adequate #line is prepended for the benefit of the debugger. You can thus single-step inside your POD documentation, yow! Using the above sample .pm file (see "pod_data_snippet"), you could do something like this in the test trailer:

my $snippet = My::Tests::Below->pod_code_snippet("create-zoinx");

# Munging $snippet a bit before running it (e.g. with regexp
# replaces) is par for the course.

my $zoinx = eval $snippet;
die $@ if $@; # If snippet fails, we want to know

# Optionally proceed to test the outcome of the snippet:

is(ref($zoinx), "Zoinx", '$zoinx is a Zoinx');

SEE ALSO

My::Module::Build knows how to remove My::Tests::Below suites at "make" or "./Build code" time, so as not to burden the compiled package with the test suite.

While I am (obviously) partial to putting tests at the bottom of the package, I also occasionally make use of classic t/*.t tests; in particular I use the same t/maintainer/*.t tests in all my CPAN modules.

BUGS

The purpose of this package is mostly a duplicate of Test::Inline and/or Pod::Tests, but I cannot join either of these efforts in the current state of CPAN affairs (Pod::Tests is not maintained, and as stated above Test::Inline is not adequate for many reasons). What I could do, however, is to standardize on similar POD markup for snippets - but the corresponding features are being reimplemented in Test::Inline as of version 2.103 (see http://search.cpan.org/~adamk/Test-Inline-2.103/lib/Test/Inline.pm#TO_DO). So I'll just wait and see.