Andrew
Andrew

Reputation: 41

Unit and integration testing in perl using Test::More - how to capture exit call?

I have this script that converts a file from csv delimited to pipe delimited (csv2pipe.pl).
The pipe delimited file then gets loaded into database.
I am trying to run unit testing for it (csv2pipe_unit_test.pl with test call like(main()...) .
Function sub main() in csv2pipe.pl has exit call if there is no parameter supplied.
Unfortunately it is not possible to capture exit call in testing call so the testing program just exits.
I cannot do eval { code }. It does not catch exit call. It will catch die and any errors but not exit.
The same with package Capture::Tiny from CPAN. It does not catch exit call.
Any ideas how to test this main() sub and catch exit call?

File: csv2pipe.pl

#!/bin/perl
use strict;
use Getopt::Std;

#--------------------------
# all work done in main sub
#--------------------------
unless (caller) { main(); exit; }

sub main {
    printf( "START---$0---%s---\n", scalar( localtime(time) ) );

    if ( scalar(@ARGV) < 4 ) {
        print "Usage: $0 -i file.csv -o file.pipe\n";
        exit;
    }

    my %options = ();
    getopts( "i:o:", \%options );
    my $file_out = $options{o} or die "Missing -o param";
    my $file_inp = $options{i} or die "Missing -i param";

    if ( !-f $file_inp ) {
        die "Could not find file to parse ($file_inp)\n";
    }

    my $row_ctr = 0;
    open( FH_inp, "<$file_inp" ) or die "Could not open file: $file_inp";
    open( FH_out, ">$file_out" ) or die "Could not open file: $file_out";

    my ( $str, $ret );
    foreach $str (<FH_inp>) {
        $row_ctr++;
        #print "str=$str";
        $ret = csv2pipe($str);
        #print "ret=$ret";
        print FH_out $ret;
    }

    close(FH_inp);
    close(FH_out);
    print "Processed $row_ctr rows\n";

    printf( "END---$0---%s---\n", scalar( localtime(time) ) );
    printf( "Program Run Time: %s second(s).\n", ( time - $^T ) );
}

# convert csv to pipe
sub csv2pipe {
    my $str = shift;
    return undef if !defined $str;
    #print ''.(caller(0))[3]."\n";
    $str =~ s/,/|/g;
    if ( $str =~ /"/ ) {
        while ( $str =~ /".*?"/ ) {
            my $beg   = $`;
            my $match = $&;
            my $end   = $';
            $match =~ s/"//g;
            $match =~ s/\|/,/g;
            $str = $beg . $match . $end;
        }
    } elsif ( $str =~ /'/ ) {
        while ( $str =~ /'.*?'/ ) {
            my $beg   = $`;
            my $match = $&;
            my $end   = $';
            $match =~ s/'//g;
            $match =~ s/\|/,/g;
            $str = $beg . $match . $end;
        }
    }
    return $str;
}

File: csv2pipe_unit_test.pl

#!/bin/perl
use strict;
use warnings;
use Test::More;

# enter main test loop
run_tests();
exit;

sub run_tests {
    printf( "START---$0---%s---\n", scalar( localtime(time) ) );

    require_ok "csv2pipe.pl";

    ok( csv2pipe('') eq '',                                 'test empty string' );
    ok( csv2pipe('a') eq 'a',                               'test one char' );
    ok( csv2pipe('a,b') eq 'a|b',                           'test two chars' );
    ok( csv2pipe('a,b,abc,def,') eq 'a|b|abc|def|',         'test multiple strings' );
    ok( csv2pipe("abc,def,abc'xyz") eq "abc|def|abc'xyz",   'test single quote in string' );
    ok( csv2pipe('abc,def,abc"xyz') eq 'abc|def|abc"xyz',   'test double quote in string' );
    ok( csv2pipe('abc,def,"abc,xyz"') eq 'abc|def|abc,xyz', 'test double quoted comma does not get converted' );
    ok( csv2pipe("abc,def,'abc,xyz'") eq 'abc|def|abc,xyz', 'test single quoted comma does not get converted' );

    # this call does not work!!!
    # exit in csv2pipe.pl function main() will exit this testing script as well
    like(
        main(),        # first  param = argument to program
        qr/Usage:/,    # second param = expected result
        'test run program with no parameter'
    );                 # 3rd parameter = test description

    print "Tests Executed: ";
    done_testing();
    printf( "END---$0---%s---\n", scalar( localtime(time) ) );
    printf( "Program Run Time: %s second(s).\n", ( time - $^T ) );
}

Normal output of testing:

C:\workspace\csv2pipe>c:\perl\bin\perl csv2pipe_unit_testing.pl
START---csv2pipe_unit_testing.pl---Fri Sep 12 15:15:47 2014---
ok 1 - require 'csv2pipe.pl';
ok 2 - test empty string
ok 3 - test one char
ok 4 - test two chars
ok 5 - test multiple strings
ok 6 - test single quote in string
ok 7 - test double quote in string
ok 8 - test double quoted comma does not get converted
ok 9 - test single quoted comma does not get converted
Tests Executed: 1..9
END---csv2pipe_unit_testing.pl---Fri Sep 12 15:15:48 2014---
Program Run Time: 1 second(s).

Output of testing when it fails in sub main() exit:

C:\workspace\csv2pipe>c:\perl\bin\perl csv2pipe_unit_testing.pl
START---csv2pipe_unit_testing.pl---Fri Sep 12 15:20:49 2014---
ok 1 - require 'csv2pipe.pl';
ok 2 - test empty string
ok 3 - test one char
ok 4 - test two chars
ok 5 - test multiple strings
ok 6 - test single quote in string
ok 7 - test double quote in string
ok 8 - test double quoted comma does not get converted
ok 9 - test single quoted comma does not get converted
START---csv2pipe_unit_testing.pl---Fri Sep 12 15:20:49 2014---
Usage: csv2pipe_unit_testing.pl -i file.csv -o file.pipe
# Tests were run but no plan was declared and done_testing() was not seen.

Upvotes: 4

Views: 528

Answers (1)

i alarmed alien
i alarmed alien

Reputation: 9530

If you can't turn your exit into a die, try Test::Trap. From the synopsis:

use Test::More;
use Test::Trap;

my @r = trap { some_code(@some_parameters) };
is ( $trap->exit, 1, 'Expecting &some_code to exit with 1' );
is ( $trap->stdout, '', 'Expecting no STDOUT' );
like ( $trap->stderr, qr/^Bad parameters; exiting\b/, 'Expecting warnings.' );

Upvotes: 4

Related Questions