6 Feb 2011

Testing your perl code - Jacinta Richardson - Perltraining.com

==== Testing your code ====

Testing cannot prove the absence of bugs, although it can help identify
them for elimination. It's impossible to write enough tests to prove
that your program is flawless. However, a comprehensive test-plan with
an appropriate arsenal of tests can assist you in your goal of making
your program defect free.


== Test::Simple ==

Writing a test is just like writing regular Perl code, and checking that
what you got is what you expected. There are a huge number of modules in
Perl which are designed to make testing that much easier. These are all
built on ``Test::Harness'' underneath. ``Test::Simple'' comes standard
with Perl and provides a great starting point.

``Test::Simple'' provides one test routine: ``ok''. You pass it an
expression, and a description. If the expression, when evaluated, is
true, the test succeeds, otherwise it fails. For example:

use Test::Simple tests => 1;

ok( 1 + 1 == 2, 'simple arithmetic works');

If we save this into a file, and run that file, we get:

1..1
ok 1 - simple arithmetic works

We can add a failing test too:

use Test::Simple tests => 2;

ok( 1 + 1 == 2, 'simple arithmetic works');
ok( 1 + 3 == 2, 'of course, this should fail');

and then we get:

$ perl testscript.pl
1..2
ok 1 - simple arithmetic works
not ok 2 - of course, this should fail
# Failed test 'of course, this should fail'
# at test.pl line 4.
# Looks like you failed 1 test of 2.

So let's consider this code. First of all we must have a plan. This
tells ``Test::Simple'' how many tests we intend to run. If we get this
number wrong, our test suite will not pass; as ``Test::Simple'' cannot
tell whether we made a mistake in our plan, or if some tests didn't run
for some other reason.

Note that we didn't need to specify test numbers, just how many tests we
have. ``Test::Simple'' will keep track of the test numbers for us.


== Test::More ==

``Test::More'' provides a richer set of functions to make your tests
even easier to write. For example, ``is'', ``isnt'', ``like'' and
``unlike'':

use Test::More tests => 8;

# Load in my module, fail if it won't load.
use_ok 'Local::People';

# Test if simple addition is working.
is( (1 + 5), 6, "Simple addition behaves");

# Let's not compare apples to oranges.
isnt( "Malus domestica",
"Citrus sinensis",
"Inequal string comparison"
);

# Test that we can create a new person
my $self;
eval { $self = Local::People->new(cache_id => 1234) };
like( $@, undef, 'Correctly created $self' );

# Does my name match this regular expression?
like ( $self->name(), qr/Jacinta/, 'Name contains Jacinta' );

# We can also provide a regular expression in a string form:
like ( $self->hobby(), '/Perl/i', 'Name contains Perl' );

# Make sure my favourite foods don't contain broad beans.
unlike ( $self->favourite_foods(),
qr/broad beans/i,
"Don't like icky foods"
);

# Test that languages matches the expected list:
my @languages = qw/Perl C PHP C++ Bash/;
is_deeply( [sort $self->languages()],
[sort @languages],
'All the languages are there'
);


== Having a plan ==

With ``Test::More'', if you're just starting your test suite, or adding
an unknown number of extra tests you can tell ``Test::More'' that you
don't *know* how many tests there will be in total:

use Test::More qw(no_plan);

Of course, once your test suite has become more stable, you should
always provide a number of tests. This allows your testing modules to
tell if your tests have stopped prematurely (which usually indicates
something has gone wrong).

You can also delay defining your plan until later:

use Test::More;

# ...

plan( tests => 3 );

This is useful if you won't know how many tests you expect to run until
some condition is decided at run-time. For example, if you have some
tests which depend on an environment variable being set, or a module
being installed. It's a mistake to set a plan, and then change it later.


== ok() vs is() ==

If you make an error either in your module, or in your test script
``ok()'' will only tell you that your test failed. ``is()'' is a lot
friendlier and will tell you both what you got, and what you said you
were expecting. For example ``ok()'' might give us:

$ perl -I lib/ testprogram.pl
1..2
ok 1 - sum() works properly
not ok 2 - product() works properly
# Failed test 'product() works properly'
# at ok.t line 10.
# Looks like you failed 1 test of 2.

Which doesn't necessarily help us spot what we've done wrong. On the
other hand, if we change our tests to use ``is()'' we'll get:

$ perl -I lib/ testprogram.pl
1..2
ok 1 - sum() works properly
not ok 2 - product() works properly
# Failed test 'product() works properly'
# at ok.t line 10.
# got: '3628800'
# expected: '3628880'
# Looks like you failed 1 test of 2.

Here we can see that our error is in the number we said we're expecting
(we've mistyped it).


== Further resources ==

There's a whole lot more to testing, and ``Test::More'' does a lot more
than we've covered here. Many articles on testing in Perl and
specifically ``Test::More'' have been written, and we recommend these in
particular:

: * Building Testing Libraries
<http://www.perl.com/pub/a/2004/05/07/testing.html>

: * Introduction to Testing
<http://www.perl.com/pub/a/2001/12/04/testing.html?page=1>

: * Perl Testing: A Developer's Notebook; by Ian Langworth and
Chromatic; O'Reilly Media, 20005

Perl Programming Best Practices 2011

==== Perl Programming Best Practices 2011 by ====

Over the next few tips, we'll be covering current best practices for
programming Perl.


== Introduction ==

In 2005, Dr Damian Conway published the very popular "Perl Best
Practices" book which details 256 "best practices" for Perl programming.
While some of these "best practices" have been further improved on in
the ensuing 5 years, many of them remain excellent practices for your
coding. If you have not yet read this book, we strongly recommend that
you do so! In this sequence of Perl tips we will cover a wide selection
of current best practices, taking into account the ideas and
modernisations that Perl has undergone..


== Some best practices are obvious ==

use strict;
use warnings;

If you're not already using ``strict'' in all of your scripts, and using
``warnings'' in almost all of your scripts, you have a lot to learn, and
should probably start with Conway's book.


= 10 year old features =

Perl has had scalar filehandles and three-argument open for more than 10
years now:

# Opening a file for reading
open(my $in_fh, "<", $input_file) or die "Failed to open: $!";

# Reading from that filehandle
while(<$in_fh>) {
# do something with $_
}

close $in_fh;

# Opening a file for writing
open(my $out_fh, ">", $output_file) or die "Failed to open: $!";

# Printing to that filehandle
print {$fh} "This text will go into my file.";

close $out_fh;

Three-argument open treats the filename literally, which protects you
from a number of potential security issues that are possible with
two-argument open. Scalar filehandles - because they are just scalars -
can be passed to subroutines, put in arrays, and used as values in
hashes. Additionally, when the scalar goes out of scope before you close
the file, Perl will automatically close the file for you. None of this
is easy with package filehandles.


= Coding standards =

Ideally every organisation you work for, or project you work on, has a
pre-established, formalised set of coding standards. While these may not
be as current as you'd like, consistency is a good thing. If you don't
have a coding standard, then now is a good time to develop one. Conway's
book is a great starting place.


== Upgrade your version ==

Perl 5.6.1 is almost 10 years old now (2001-Apr-08). Perl 5.8.2 is 7
years old (2003-Nov-05). Perl 5.10.0 is 3 years old (2007-Dec-18). Perl
5.8.9 is 2 years old (2008-Dec-14). Perl 5.10.1 is 1.5 years old
(2009-Aug-22) Perl 5.12.2 is 4 months old (2010-Sep-06).

Upgrade! At the very, very least, use one of the Perl 5.8.x versions,
but if you can, upgrade to either 5.10.1 or 5.12.2. Why? Because a whole
lot of wonderful, new, awesome and *modern* features have been
incorporated into Perl in the last few years.

To use the features of your modern version of Perl, just specify your
minimum version:

use v5.10.1; # get all the things from Perl 5.10.1


= Perl 5.12.x =

By specifying any of the Perl 5.12 versions, ``strict'' is automatically
turned on by default. Thus this:

use v5.12.2;

is the same as saying:

use v5.12.2;
use strict;

This is a great improvement.


== New features ==

With Perl 5.10.0 we received a host of new features. Some of these are
mentioned here:


= say =

``say'' gives us ``print'' with an attached newline. Instead of:

print "Hello World!\n";

print "$_\n" foreach @list;

we can write:

say "Hello World!";

say foreach @list;

it's shorter, neater, and gets rid of that ugly $_.


= // (defined-or) =

Consider this subroutine:

sub item_sale {
my ($product, $price) = @_;

$price ||= $product->cost;

...
}

This example is short, neat, and almost correct. But how do we signal
that this item is being provided for free without changing the $product
object? If we pass in zero, then that's a false value, so we're going to
get the full product cost.

We could rewrite this to be:

sub item_sale {
my ($product, $price) = @_;

unless( defined($price) ) {
$price = $product->cost;
}

...
}

Or, in Perl 5.10.0 and above we can use defined-or. Defined-or works
just like regular OR, except that it tests for whether the value is
defined, rather than true.

sub item_sale {
my ($product, $price) = @_;

$price //= $product->cost;

...
}


= state =

``state'' allows us to create variables which remember their state.
These are like ``static'' variables in C. Imagine the following code to
ensure that we only show 100 images:

my $images_allowed = 100;

sub view_image {
die "Too many images" unless $images_allowed-- > 0;

...
}

As it is, other subroutines here could be used to change
$images_allowed. To prevent that, we could wrap it into a closure:

{
my $images_allowed = 100;

sub view_image {
die "Too many images" unless $images_allowed-- > 0;

...
}
}

Or, we can use a state variable:

sub view_image {
state $images_allowed = 100;
die "Too many images" unless $images_allowed-- > 0;

...
}


= given-when =

There's now a native switch for Perl, except that it's very, very cool.

given ($has) {
when (/ninja/) { $ninjas++; }
when (/pirate/) { $pirates++; }
when (/robot/) { $robots++; }
when (@other) { $other_cool_things++; }
when (%interesting) { $interesting++; }
default { $boring++; }
}

This says smart match the value in $has. If it matches against any of
the first three regular expressions, increment that variable. Otherwise
if it is an element of @other increment ``$other_cool_things++'',
otherwise if it is a key in %interesting increment $interesting. If it
matches none of these comparisons, increment $boring.

The given-when can also be used with ``foreach'':

foreach (@input) {
when (/ninja/) { $ninjas++; }
when (/pirate/) { $pirates++; }
when (/robot/) { $robots++; }
when (@other) { $other_cool_things++; }
when (%interesting) { $interesting++; }
default { $boring++; }
}


= New features: smart matching =

given-when uses smart matching to work. Smart match allows us to compare
things to determine if they match, in the most useful way possible:

$foo ~~ undef; # is $foo undefined?

$foo ~~ @array; # is $foo in @array?

$foo ~~ %hash; # is $foo a key in %hash?

$foo ~~ $coderef; # does $coderef->($foo) return true?

$foo ~~ qr{$re}; # does $foo pattern match $re?

$foo ~~ $bar; # does $foo equal $bar?


== Next ==

There are many other best practices still to be covered. In our next tip
we'll discuss exception handling, and how you can test a wide variety of
Perl versions from your home directory.