Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Re: How to unit test a script packaged in a module distribution?

by xdg (Monsignor)
on Apr 27, 2007 at 21:15 UTC ( [id://612455]=note: print w/replies, xml ) Need Help??


in reply to How to unit test a script packaged in a module distribution?

execute it via the "system" call

It's easy to do this non-portably. For example, if you rely on the script being executable and having a shebang line, this won't work on Windows unless .pl file associations have been set up.

I've done a fair amount of script testing and always set up an explicit call to perl, usually with Probe::Perl and relying on IPC::Run3 to execute the program and manage input and output.

For example:

use strict; use File::Spec; use IPC::Run3 qw/run3/; use Probe::Perl; use Test::More; plan tests => 2 ; my $perl = Probe::Perl->find_perl_interpreter; my $script = File::Spec->catfile(qw/scripts foo.pl/); my ($got_stdout, $got_stderr); ok( -r $script, "foo.pl script readable" ); # run and capture output run3 "$perl $script", undef, \$got_stdout, \$got_stderr; is( $got_stdout, $expected_stdout), "foo.pl program output" );

You can see an example like that in my Tee distribution in 02_tee_script.t.

In some cases, I've written an entire test helper class to manage repetitive tests on a script. In Pod::WikiDoc, I have a file called t/CLI.pm and I use it for repeated calls to a script with different command line options, plus it automatically captures output and has tests for whether it ran successfully or not.

use t::CLI; my $script = File::Spec->catfile( "scripts", "wikidoc" ); my $wikidoc = t::CLI->new($script); # setup $input_file and $output_file $wikidoc->runs_ok( "$input_file", "$output_file" ); $wikidoc->stdout_like( qr/Extracting Pod from \Q$input_file\E/, "'wikidoc file file' status message" );

Here's is a copy of t::CLI:

package t::CLI; use strict; use warnings; use Test::Builder; use Probe::Perl; use Cwd qw( abs_path ); use IPC::Run3; use File::Basename qw( basename ); use File::Spec; #use POSIX qw( WEXITSTATUS ); my $Test = Test::Builder->new; my $pp = Probe::Perl->new; my $perl = abs_path($pp->find_perl_interpreter); my $cwd = abs_path( "." ); my $coverdb = File::Spec->catdir($cwd,"cover_db"); my $cover = index( ($ENV{HARNESS_PERL_SWITCHES} || ''), '-MDevel::Cove +r' ) < 0 ? '' : "-MDevel::Cover=-db,$coverdb" ; #--------------------------------------------------------------------- +-----# # Main API #--------------------------------------------------------------------- +-----# sub new { my ($class, $program, @default_args ) = @_; my $self = bless {}, $class; $self->program( $program ); $self->default_args( [@default_args] ); return $self; } # returns success or failure; exit_code is opposite (0 is good); actua +l error # message is in @$ sub run { my ($self, @args) = @_; my ($stdout, $stderr); my $stdin = $self->stdin; my @cmd = ( $perl, "-Mblib=$cwd", # must hard code this in case curdir changed ( $cover ? $cover : () ), $self->{program}, @{ $self->default_args() }, @args, ); eval { run3 \@cmd, \$stdin, \$stdout, \$stderr; $self->exit_code( $? >> 8 || 0 ); }; my $result = $@ eq q{} ? 1 : 0; $self->stdout($stdout); $self->stderr($stderr); $@ .= "(running '@cmd')"; return $result; } #--------------------------------------------------------------------- +-----# # Accessors #--------------------------------------------------------------------- +-----# sub program { my ($self, $filename) = @_; if (defined $filename) { die "Can't find $filename" if ! -e $filename; $self->{program} = abs_path($filename); } return basename( $self->{program} ); } BEGIN { my $evaltext = << 'CODE'; sub { $_[0]->{PROP} = $_[1] if @_ > 1; return $_[0]->{PROP}; } CODE for ( qw( exit_code stdin stdout stderr default_args)) { no strict 'refs'; (my $sub = $evaltext) =~ s/PROP/$_/g; *{__PACKAGE__ . "::$_"} = eval $sub; } } #--------------------------------------------------------------------- +-----# # Testing functions #--------------------------------------------------------------------- +-----# sub runs_ok { my ($self, @args) = @_; my $label = "Ran " . $self->program() . " with " . (@args && $args[0] ne q{} ? "args '@args'" : "no args" + ) . " without error"; my $runs = $self->run(@args); die $@ if ! $runs; my $ok = $Test->ok( ! $self->exit_code, $label ); if ( ! $ok ) { $Test->diag( "Exit code: " . $self->exit_code . "\n"); $Test->diag( "STDERR: " . $self->stderr . "\n") if $self->stde +rr; $Test->diag( "STDOUT: " . $self->stdout . "\n") if $self->stdo +ut; } return $ok; } sub dies_ok { my ($self, @args) = @_; my $label = $self->program . " with " . (@args && $args[0] ne q{} ? "args '@args'" : "no args" + ) . " ended with an error"; my $runs = $self->run(@args); die $@ if ! $runs; my $ok = $Test->ok( $self->exit_code, $label ); if ( ! $ok ) { $Test->diag( "Exit code: " . $self->exit_code . "\n"); } return $ok; } sub exits_with { my ($self, $expect, @args) = @_; my $label = $self->program . " with " . (@args && $args[0] ne q{} ? "args '@args'" : "no args" + ) . " ended with exit code $expect"; my $runs = $self->run(@args); die $@ if ! $runs; return $Test->is_num( $self->exit_code, $expect, $label ); } sub stdout_is { my ($self, $expect, $label) = @_; return $Test->is_eq( $self->stdout, $expect, $label ? "... $label" : "... " . $self +->program . " had correct output to STDOUT" ); } sub stdout_like { my ($self, $expect, $label) = @_; return $Test->like( $self->stdout, $expect, $label ? "... $label" : "... " . $self->program . " had correct output to STDOUT +" ); } sub stderr_is { my ($self, $expect, $label) = @_; return $Test->is_eq( $self->stderr, $expect, $label ? "... $label" : "... " . $self->program . " had correct output to STDERR +" ); } sub stderr_like { my ($self, $expect, $label) = @_; return $Test->like( $self->stderr, $expect, $label ? "... $label" : "... " . $self->program . " had correct output to STDERR +" ); } 1; #true

You can see that in use in t/70-wikidoc-script.t.

I hope that gives you some useful ideas.

-xdg

Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Replies are listed 'Best First'.
Re^2: How to unit test a script packaged in a module distribution?
by vbar (Novice) on Apr 28, 2007 at 13:00 UTC
    Yes, I'm definitely going to give IPC::Run3 and Probe::Perl a try - thanks a lot! The tests work on my machine now - we'll see how CPAN handles them...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://612455]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (2)
As of 2024-04-25 02:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found