Do you use let's encrypt?





7122 votes ~ 23 comments

 

Writing Perl test cases

Posted by Steve on Wed 23 Nov 2005 at 14:02

Tags: ,

If you've ever automated any operations with Perl you'll most likely have written a small script or two, and then left them to run. But how do you know that your code is correct? The answer is to write some test cases and verify they perform correctly. With Perls test facilities this is a simple thing to do.

Most large Perl projects have a test suite, and so do many of the common modules you'll have installed.

The traditional approach to installing an external module from CPAN is to run the following commands:

perl Makefile.PL
make
make test
su -c 'make install'

Here we're going to look at one way of implementing the "make test" handling, using the standard perl modules Test::Harness and Test::More.

These modules will already be installed upon your system if you've got the Perl modules package installed.

To get started we'll look at a simple example. Suppose your script uses some non-standard Perl modules that suggests the very first test you write should verify that those are available. That way if you attempt to run the program upon a new host you'll immediately know whether you need to install these non-standard modules.

As an example we'll consider the project myproject using the modules Mail::Verify and HTML::Template modules (both very commonly used, but not contained in Perl's standard installation). The first step is to create a directory to contain our test cases.

Traditionally tests are stored beneath a project in a directory called tests, each test is in its own file with the .t suffix.

For the first test we'll save the following to myproject/tests/mymodules.t:

#!/usr/bin/perl -w
#
# ~/myproject/tests/modules.t
#
#  Test that all the Perl modules we require are available.
#

use Test::More tests => 4;

use_ok( 'Mail::Verify' ); 
require_ok( 'Mail::Verify' );


use_ok( 'HTML::Template' );
require_ok( 'HTML::Template' );

Running the test is as simple as :

skx@itchy:~/mymproject$ perl tests/modules.t
1..4
ok 1 - use Mail::Verify;
ok 2 - require Mail::Verify;
ok 3 - use HTML::Template;
ok 4 - require HTML::Template;

This shows the four tests each running successfully. Running the same test upon a host which doesn't have the Mail::Verify module installed shows the following output:

skx@scratchy:~/mymproject$ perl tests/modules.t
1..4
not ok 1 - use Mail::Verify;
#     Failed test (tests/modules.t at line 10)
#     Tried to use 'Mail::Verify'.
# BEGIN failed--compilation aborted at tests/modules.t line 10.
not ok 2 - require Mail::Verify;
#     Failed test (tests/modules.t at line 11)
#     Tried to require 'Mail::Verify'.
ok 3 - use HTML::Template;
ok 4 - require HTML::Template;
# Looks like you failed 2 tests of 4.

In our test we've not done anything special, just attempted to load the modules we use. This was accomplished with the use_ok and require_ok functions.

In addition to these simple testing functions we can also use the more useful "ok" call to test that something is OK.

If we create another test we can see how this works.

#!/usr/bin/perl -w
#
# ~/myproject/tests/foo.t
#

use Test::More tests => 3;

ok( 1 + 1 == 2, "One and one is two!" );
ok( "Steve" =~ /steve/i, "Steve matches steve" );
ok( defined( `hostname` ), "Hostname produced .. something" );

This demonstrates testing various things. Note that the second argument to the ok function is optional - it merely contains text to display next to the test when the code is executed as you can see here:

skx@itchy:~/myproject/tests$ perl foo.t 
1..3
ok 1 - One and one is two!
ok 2 - Steve matches steve
ok 3 - Hostname produced .. something

One useful thing that you might have spotted is the number of tests that we're expecting to run - set in the code with "use Test::More tests => 3". If you don't wish to keep updating this number you can instead start your tests with the following:

use Test::More qw( no_plan );

This tells the driver that we don't care how many test cases actually run.

Once you've reached a point where you have several test cases you'll want to run them all easily. This is where the Test::Harness driver comes in handy. It allows you to run multiple tests easily.

I usally create the following tests/Makefile:

#
#  ~/myproject/tests/Makefile
#
#  Run our tests, either quietly or with verbose output
#
#  This is a Makefile - so the indented lines start with a TAB!
#
#  Whoever came up with the wonderful idea that whitespace was significant..?
#
all:
        perl -MTest::Harness -e '$$Test::Harness::verbose=0; runtests @ARGV;' *.t
verbose:
        perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV;' *.t
clean:
        find . -name '*~' -exec rm -f \{\} \;

This allows me to run the tests by changing to the tests/ subdirectory and executing "make":

skx@itchy:~/myproject/tests$ make
perl -MTest::Harness -e '$Test::Harness::verbose=0; runtests @ARGV;' *.t
foo........ok                                                                
modules....ok                                                                
All tests successful.
Files=2, Tests=7,  0 wallclock secs ( 0.17 cusr +  0.01 csys =  0.18 CPU)

Running verbosely is accomplished via "make verbose":

skx@itchy:~/myproject/tests$ make verbose 
perl -MTest::Harness -e '$Test::Harness::verbose=1; runtests @ARGV;' *.t
foo........1..3
ok 1 - One and one is two!
ok 2 - Steve matches steve
ok 3 - Hostname produced .. something
ok
modules....1..4
ok 1 - use Mail::Verify;
ok 2 - require Mail::Verify;
ok 3 - use HTML::Template;
ok 4 - require HTML::Template;
ok
All tests successful.
Files=2, Tests=7,  0 wallclock secs ( 0.17 cusr +  0.01 csys =  0.18 CPU)

To finish I'll show a simple test that is similar to one I use upon this site:

#!/usr/bin/perl -w -I..
#
#  Test that we can get, edit, and delete a static page.
#

use Test::More qw( no_plan );

#
#  Load the modules we use.
#
BEGIN { use_ok( 'Yawns::About'); }
require_ok( 'Yawns::About' );


#
#  Pick a random page name
my $name = join ( '', map {('a'..'z')[rand 26]} 0..7 );


#
#  Work with the about pages.
#
my $about = Yawns::About->new();
isa_ok( $about, "Yawns::About" );


#
#  Get the text and ensure it is empty
#
my $text = $about->get( name => $name );
ok( (!defined($text) ), "The random about page is empty" );

#
#  Save some random contents
#
my $contents = join ( '', map {('a'..'z')[rand 26]} 0..20 );
$about->set( name => $name,
            text => $contents );

#
#  Now ensure they are saved
#
ok( defined( $about->get( name => $name ) ), 
        " After setting text the page is non-empty." );

ok( ( $contents eq $about->get( name => $name ) ),
        " And the text matches what we set." );

#
#  Delete the page
#
$about->delete( name => $name );

#
#  Verify the page is gone.
#
$text = $about->get( name => $name );
ok( (!defined($text) ), "The random is empty after deletion." );

This test is used upon this host to ensure that the code which displays static pages from the database is working correctly. (Implementation detail - this code is used to display the site FAQ, and other similar pages.)

Running the code shows the following output:

skx@itchy:~/debian-administration/tests$ perl yawns-about.t 
ok 1 - use Yawns::About;
ok 2 - require Yawns::About;
ok 3 - The object isa Yawns::About
ok 4 - The random about page is empty
ok 5 -  After setting text the page is non-empty.
ok 6 -  And the text matches what we set.
ok 7 - The random is empty after deletion.
1..7
What To Test?

Once you start testing code you'll realise it makes much more sense to split your code into "modules", rather than using one large monolithic script.

The reason for this is twofold:

  • It makes your code more reusable.
  • It allows you to call your functions from outside the main driver script - which is precisely what you need to implement tests.
More Testing

If you wish to start adding tests to your code you'd be well advised to install the perl-doc package. This allows you to read your perl documentation with ease:

perldoc module::name

So to read the full details on the Test::More module you can execute:

perldoc Test::More
perldoc Test::Simple
perldoc Test::Harness

It also gives you access to perl function documentation:

skx@itchy:~$ perldoc -f split
      split /PATTERN/,EXPR,LIMIT
       split /PATTERN/,EXPR
       split /PATTERN/
       split   Splits the string EXPR into a list of strings and returns that
               list.  By default, empty leading fields are preserved, and
               empty trailing ones are deleted.  (If all fields are empty,
               they are considered to be trailing.)
               ...
               ...
               & etc

 

 


Re: Writing Perl test cases
Posted by drue (84.139.xx.xx) on Fri 25 Nov 2005 at 09:38
Very nice and helpful intro!
The last topic (perldoc -f perlfuncname) was really new to me ... I wished I knew it much earlier ;-))

Thanks!

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Steve (82.41.xx.xx) on Sun 27 Nov 2005 at 02:44
[ View Steve's Scratchpad | View Weblogs ]

If you use cperl-mode under Emacs you can lookup documentation for functions directly with "M-x cperl-perldoc".

You can also access this from the menu with "Perl | Perldocs".

If you're not using CPerl mode you can try it out by adding the following to your ~/.emacs file (creating it if it doesn't exist):

;;  We always prefer CPerl mode to Perl mode.
(fset 'perl-mode 'cperl-mode)

Steve

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by alanhaggai (59.91.xx.xx) on Mon 25 Aug 2008 at 02:42
In Vim, pressing Shift + K opens the perldoc page for the function.

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Anonymous (213.164.xx.xx) on Fri 25 Nov 2005 at 10:21
Good article.

For installing modules, I first check apt, then resort to "perl -eshell -MCPAN" then "install Module".

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Steve (82.41.xx.xx) on Fri 25 Nov 2005 at 14:51
[ View Steve's Scratchpad | View Weblogs ]

I don't like mixing CPAN and packages - so I create packages of Perl modules by running:

apt-get install dh-make-perl
dh-make-perl --build --notest --cpan foo::bar
sudo dpkg --install libfoo-bar-perl*.deb

That downloads and builds the module, creating a Debian package of the result.

Steve

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Anonymous (24.21.xx.xx) on Sun 27 Nov 2005 at 15:59
ok( ( $contents eq $about->get( name => $name ) ),
        " And the text matches what we set." );
should be
is $about->get(name => $name), $contents, "contents match";
so that a failure will report the expected value, and the actual value. In general, if you're using "ok", you should consider a stronger form.

-- Randal Schwartz, merlyn@stonehenge.com, http://www.stonehenge.com/merlyn/

(Yes, *that* Randal.)

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Steve (82.41.xx.xx) on Sun 27 Nov 2005 at 19:04
[ View Steve's Scratchpad | View Weblogs ]

Cheers for that (I'm well aware of who you are, from Perlmonks and other places :).

You are correct - I just used ok to keep the examples nice and simple without explaining more of the test primitives than I needed to.

The fine documentation will point people to is and the other available methods.

Steve

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Anonymous (192.8.xx.xx) on Wed 2 Apr 2008 at 08:33
nice one

[ Parent | Reply to this comment ]

Re: Writing Perl test cases
Posted by Anonymous (129.33.xx.xx) on Mon 2 Apr 2012 at 20:00
Love the article, thank you very much !!

I have an almost unrelated question though: can we go by, by not using "make" ??
Our diverse group has people without any knowledger of make and we don't want to introduce yet another possible source of frustration. Everybody favorable, to not use using make, raise your hand. Others, don't say anything, please. :D

[ Parent | Reply to this comment ]