rpetre has asked for the wisdom of the Perl Monks concerning the following question:

After watching a lot of test-related talks at YAPC::EU::2009, I finally got 'infected' with the "Code Should First Run In A Test" virus, so i'm currently thinking of a way to convert various utility scripts to a testable form. I think I have nailed a workable solution, but some monk wisdom would be welcome.

So far, the requirements are:

For now, i ended up with something like this (inspired mainly by How a script becomes a module):
######## foo.pl ############ #!/usr/bin/perl use strict; use warnings; MyApp->import( 'run' ); run() unless caller(); package MyApp; BEGIN { use Exporter 'import'; our @EXPORT_OK = qw(run mysub); } sub run { my $result = mysub(); print "$result\n"; } sub mysub { return "mysub called"; } ########## foo.t ############# #!/usr/bin/perl use strict; use warnings; use Test::Simple tests => 1; require 'foo.pl'; MyApp->import(qw(mysub)); ok ( mysub() =~ 'mysub' , '&mysub successfully imported' );
Now I'm satisfied:

All this being said, I think it's clear enough to show to people less Perl-inclined and to help preach the benefits of testing, even for sysadmin scripts. Any improvement ideas?

Replies are listed 'Best First'.
Re: Guidelines for creating self-contained testable scripts
by Marshall (Canon) on Aug 11, 2009 at 05:54 UTC
    There are lots of ways to test things in Perl and ways for Perl to test other things too!

    Another idea for you...

    On my last Perl project which had a number of .pm modules (about 10 .pm files), I set each .pm file up with a standard test() subroutine whose name ('test') was not exported. Then also something like this: my $DEBUG =0; test() if $DEBUG;.

    When I'm working with the module, adding functions or whatever, I set "my $DEBUG=1;" and my edit environment allows me to just hit a "run Perl" button and I run the code and see what happens. I add test cases to test() to get the module working. I don't try to call the subs in that .pm file from other programs until I have a good result from this module's test() function. So this .pm file looks like a .pl program with $DEBUG on.

    To run all test subs in all modules, I just call FULL_PM_NAME::test() on each .pm file. Having some sort of naming convention can help. Maybe to test some shell scripts, you look in a directory and run the "program.test" executable of any executable "program" which has a corresponding "program.test", etc.

    This is a simple strategy suited for small projects. When the project gets larger and test cases so complex that adding them all into the source .pm file doesn't make sense, then of course things change. But I think even in that situation a case could be made for including a "kick-the-tires", "smoke-test" into the .pm file.

    I think testing is a broad subject and there is no "one size" fits all.

Re: Guidelines for creating self-contained testable scripts
by Anonymous Monk on Aug 11, 2009 at 02:11 UTC

      Nice hint, but at the moment I'm not aiming (yet) to create a distribution. If possible, I want the whole procedure of 'pick the script.pl file from SVN and update it on all machines by rsync' to remain unchanged, just that next to it appear some tests that can be run when updating the script.

      As soon as people start to adopt the testing ideology, we'll move to moving routines into utility standalone modules.

        I think you should start now, better than incremental shock :)
        cpan MySuperFantasticScript
Re: Guidelines for creating self-contained testable scripts
by pileofrogs (Priest) on Aug 12, 2009 at 17:17 UTC

    What about the obvious..

    use Test::more qw(2); sub it_worked { # check to see if myscript worked # EG, did it update file X by adding line Y } ok( !system('./bin/myscript'), "didn't blow chunks"); ok( it_worked,'it worked');
      That's what I would do.

      That's easy, but the core of the script might do some things I wouldn't want to run while testing (like deleting files or dropping database tables or whatever), that's why I was trying to provide a way to test only specific parts.

      But yeah, you're right too :), thanks.