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.
I like to use Test::Group for the unit perlmodlib-style unit tests, because counting and recounting my tests drives me nuts :-). However My::Tests::Below itself is testing-framework agnostic (its own self-test suite, for instance, uses only plain old Test::More).
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
mainor 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::BelowPOD markup lines obeys the following rules:the first token after
My::Tests::Belowis 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
beginandend, 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.