Hands up how many of you have uploaded modules to CPAN? Good. Hands up how many of you have made a stupid mistake in the module's documentation and had to upload another version? Too many of us it would seem.
I'm sad to say I've done this more than a couple of times. It's so easy to write a chunk of code directly into your pod and get lazy enough to never check that it compiles, or check that that does the right thing. Copying and pasting example code back and forth to your test suite is a tiresome task and it's easy to forget to do it. It's also entirely possible to document a feature that you never got round to writing tests for, and doesn't even work!
This is where Test::Inline comes in. It allows you to write tests directly in your pod. It can check if the code you've written compiles okay, so you can write a test for a feature as soon as you've written in the documentation that your code can do something. It also allows you to markup the code examples you're including in your documentation to indicate that the testing routines should check that they compile and that they produce the correct output.
All these tests can be extracted with a utility program that creates a test script that ships with your code, meaning you never need have to manage two separate files again. How handy.
Let's write a simple module to get the modules published in the Perl advent calendar.
package WWW::PerlAdventCalendar;
# turn on perl's safety features use strict;
# declare a version use vars qw($VERSION); $VERSION = 0.01;
use LWP::Simple qw(get); use XML::RSS;
=head1 NAME
WWW::PerlAdventCalendar - get module for the day
=head1 SYNOPSIS
use WWW::PerlAdventCalendar;
my $advent = WWW::PerlAdventCalendar->new(); my $module = $advent->module_for(2002,3);
=head1 DESCRIPTION
A simple interface that works out what modules have been published on the Perl advent calendar for a year. This module uses the XML RDF streams that.
Using it is simple. All you need do is create the object, and then ask the object what the module is for a year and date.
# print out what module was on the 7th December 2003 my $advent = WWW::PerlAdventCalendar->new(); print $advent->module_for(2003,7);
Each RSS feed is downloaded once (and only once) on demand and cached. To clear a year's cache, all you have to do is call the clear_cache method with the year in question
$advent->clear_cache(2003);
=cut
# basic standard constructor sub new { my $class = shift; return bless {}, $class }
# simply look up and return the correct module, downloading # and parsing the year's rss if we need to sub module_for { my $self = shift; my $year = shift; my $day = shift;
# get the year if we haven't got it already unless ($self->{cache}{ $year }) { $self->_parse_year($year) };
# return the module for that day return $self->{cache}{ $year }[ $day ] }
# download and parse year sub _parse_year { my $self = shift; my $year = shift;
# get the year my $xml = get "http://perladvent.org/perladvent${year}.rdf" or die "Can't get year '$year'";
# parse it into a rss file my $rss = XML::RSS->new(); $rss->parse($xml);
# fill up the year my $count = 1; for my $item (reverse @{ $rss->{items} }) { # extract out the module name ($self->{cache}{ $year }[ $count++ ]) = $item->{title} =~ /\d+[a-z]+ - (.*)$/; } }
sub clear_cache { my $self = shift; my $year = shift;
delete $self->{cache}{ $year }; }
=head1 AUTHOR
Written by Mark Fowler E<lt>mark@twoshortplanks.comE<gt>
Copyright Mark Fowler 2003. All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
=head1 BUGS
None known.
=head1 SEE ALSO
L<XML::RSS>, L<LWP::Simple>
=cut
1;
You can see how there's several code snippets talked about in the documentation that I'd like to check actually function as I expect them to. What we need to do is markup some of the text using extra notation so that Test::Inline knows what to extract. We start with we markup the text in our synopsis:
=head1 SYNOPSIS
=for example begin
use WWW::PerlAdventCalendar;
my $advent = WWW::PerlAdventCalendar->new(); my $module = $advent->module_for(2002,3);
=for example end
=head1 DESCRIPTION
It's important to include a blank line after each of the =for
directives. This pod will be rendered by perldoc (and pod2html and
all the other pod viewers) no differently to the previous version;
however we can now use the pod2test
to extract these tests and
install them into a file:
bask-2.05b$ mkdir t bash-2.05b$ pod2test lib/WWW/PerlAdventCalendar.pm > t/01basic.t
This test file can be then run like any other test. The amazing thing is that because the outputted test script doesn't rely on Test::Inline you don't even need to get your end users to install it. You only need the module installed in order to extract the test from the pod.
When we run the test we get the rather satisfactory result indicating that our code compiled okay and didn't contain any syntax errors.
bash-2.05b$ perl -Ilib t/01basic.t ok 1 - example from line 20 1..1
If we want to test the code any closer, to make sure the code actually
did the job it was supposed to we need to add extra test code that isn't
printed out when the pod is viewed through a normal pod renderer. We
do this with the =for example_testing
notation:
=head1 SYNOPSIS
=for example begin
use WWW::PerlAdventCalendar;
my $advent = WWW::PerlAdventCalendar->new(); my $module = $advent->module_for(2002,3);
=for example end
=for example_testing is($module,"DBD::SQLite","3rd dec 2002 is DBD::SQLite");
=head1 DESCRIPTION
All the code we want to run to check must immediately follow the
=for
statement and you can't leave any blank lines - leaving blank
lines would indicate to pod that the current =for example_testing
has ended and the rest of the code should be treated as normal pod and
rendered rather than converted into tests. Anyway, it all works:
bash-2.05b$ perl -Ilib t/01basic.t ok 1 - example from line 20 ok 2 - 3rd dec 2002 is DBD::SQLite 1..2
The second code example we want to test is about half way down the page. This time we want to check it compiles and prints out the correct thing:
=for example begin
# print out what module was on the 7th December 2003 my $advent = WWW::PerlAdventCalendar->new(); print $advent->module_for(2003,7);
=for example end
=for example_testing is($_STDOUT_,"Attribute::Handlers", "prints out A::H");
All the output that is printed to STDOUT
is captured by the code
Test::Inline produces and is stored in $_STDOUT_
($_STDERR_
works similarly) enabling you to run tests against it.
The final chunk of code in our pod needs setup before it's compiled as
it's just a simple one line example that assumes $advent
has
already been created. If we're going to test that the cache is
cleared like it says, we need to create $advent
and download a page
before we run the example code:
=for example my $advent = WWW::PerlAdventCalendar->new(); $advent->module_for(2003,3);
=for example begin
$advent->clear_cache(2003);
=for example end
=for example_testing ok(!$advent->{cache}{2003},"cache empty");
And that's the examples in the code tested. It's still probably a good idea to write an extra test suite to cover the cases that haven't been covered here (for example, does the code work if we clear a cache that doesn't exist? Does it throw an error correctly in getting an erroneous year?) However, all the examples we've written in the pod work and we can be assured anyone using the documentation will have a solid basis to build their code on.