Unit Testing My Library Graceful Exit

[prev UnitTestingMyLibraryPrintHeader | next UnitTestingMyLibraryReturnError | top UnitTestingMyLibrary ]

The next subroutine for testing presents some new wrinkles.
 sub gracefulExit {
        # print the footer
	&printFooter;
        # close the html
        print $output->end_html;
	# disconnect the handle to the database
        if ($gSth) { $gSth->finish; }
	$gDBhandle->disconnect;
        # quit gracefully
	exit;
 }
One problem is the exit statement here. If you call this subroutine from a TestSuite, it stops execution of the TestRunner. This makes it necessary to go into the copy of MyLibrary.pm that's being tested and comment out the exit line.

Another complication is that this subroutine calls another one, so we have to take the needs and influence of MyLibrary::printFooter. Fortunately, it's easy to factor out this particular subroutine. If we don't even define the one variable it expects, it executes without complaint and doesn't produce any output.

Furthermore, this subroutine presents new expectations (a database handle and possibly a statement handle) and has other SideEffects than printing output. (As an aside, I would say the main reason I prefer the PythonLanguage over the PerlLanguage is not the executable-line-noise saw, but that Perl is all SideEffects. -- ChrisGray) In the absence of a method for setting up MockObjects for the handles it is possible to set up an empty MySQL database and run some inconsequential SQL statement.

So a working TestCase looks like this
 # ---------- tests for &MyLibrary::gracefulExit ----------
 package gracefulExit;
 use Test::Unit;
 require "../MyLibrary.pm";
 use CGI;
 use DBI;
 use IO::String;
 sub set_up {
    $io = IO::String->new($result);
    $old_handle = select($io);
    $MyLibrary::output = new CGI("");
    $MyLibrary::gDBhandle = DBI->connect("DBI:mysql:mylibrary_test:localhost", \"test", "test");
    $MyLibrary::gSth = $MyLibrary::gDBhandle->prepare("show tables");
    $MyLibrary::gSth->execute;
 }
 sub tear_down {
    select($old_handle);
    $io = undef;
    $result = undef;
    $old_handle = undef;
    $MyLibrary::output = undef;
    $MyLibrary::gSth->finish;
    $MyLibrary::gDBhandle->disconnect;
    $MyLibrary::gSth = undef;
    $MyLibrary::gDBhandle = undef;
 }
 sub test_footer {
    my $expected = "</body></html>";
    &MyLibrary::gracefulExit;
    assert(! $MyLibrary::gSth->{Active}, "gSth still active");
    assert(! $MyLibrary::gDBhandle->{Active}, "gDBhandle still active");
    assert($result eq $expected, "gracefulExit failed with gFooter given");
 }
Oops! ThreeStrikesAndYouRefactor. We've repeated the same lines for capturing STDOUT three times. We need to rethink that.

So using ExtractMethod we add the following subroutine.
 # ---------- subroutines ----------
 package subroutines;
 sub trap_stdout {
    my($unit_ref, $result, $io, $old_handle);
    $unit_ref = $_[0];
    $io = IO::String->new($result);
    $old_handle = select($io);
    &$unit_ref;
    select($old_handle);
    return $result;
 }
And, for instance, the TestCase for MyLibrary::printFooter becomes.
 # ---------- tests for &MyLibrary::printFooter ----------
 package printFooter;
 use Test::Unit;
 require "../MyLibrary.pm";
 use IO::String;
 sub set_up {
    $MyLibrary::gFooter = "***FOOTER TEXT***";
 }
 sub tear_down {
    $MyLibrary::gFooter = undef;
 }
 sub test_footer {
    my $expected = $MyLibrary::gFooter;
    my $result =  &subroutines::trap_stdout(\&MyLibrary::printFooter);
    assert($result eq $expected, "printFooter failed when gFooter given");
 }
 sub test_no_footer {
    my $expected = "";
    $MyLibrary::gFooter = undef;
    my $result = &subroutines::trap_stdout(\&MyLibrary::printFooter);
    assert($result eq $expected, "printFooter failed when no gFooter given");
 }
And similarly for the other TestCases.


So within the MyLibrary.pm module the five or so instances of the line:
 exit;
get replaced with:
 &Exit;
and we add a small subroutine to the file:
 sub Exit {
    exit;
 }
And the subroutine trap_stdout introduced above and refined in UnitTestingMyLibraryReturnError in the TestSuite becomes:
 sub trap_stdout {
    my($unit_ref, $args, $result, $io, $old_handle);
    $unit_ref = $_[0];
    $args = $_[1];
    print STDOUT @args;
    $io = IO::String->new($result);
    $old_handle = select($io);
    local *MyLibrary::Exit = sub { ; };
    &$unit_ref(@$args);
    select($old_handle);
    return $result;
 }
and we a version of the module that should run the same as the old version but can be tested without shutting down the TestSuite in mid-test.


CategoryTesting


EditText of this page (last edited July 1, 2003) or FindPage with title or text search