Section 18.3.  Testing STDOUT or STDERR

Table of Contents

18.3. Testing STDOUT or STDERR

One advantage to using the ok( ) functions (and friends) is that they don't write to STDOUT directly, but to a filehandle secretly duplicated from STDOUT when our test script begins. If we don't change STDOUT in our program, of course, this is a moot point. But let's say we wanted to test a routine that writes something to STDOUT, such as making sure a horse eats properly:

use Test::More 'no_plan';
use_ok 'Horse';
isa_ok(my $trigger = Horse->named('Trigger'), 'Horse');

open STDOUT, ">test.out" or die "Could not redirect STDOUT! $!";
close STDOUT;

open T, "test.out" or die "Could not read from test.out! $!";
my @contents = <T>;
close T;
is(join("", @contents), "Trigger eats hay.\n", "Trigger ate properly");

END { unlink "test.out" }  # clean up after the horses

Note that just before we start testing the eat method, we (re)open STDOUT to our temporary output file. The output from this method ends up in the test.out file. We bring the contents of that file in and give it to the is( ) function. Even though we've closed STDOUT, the is( ) function can still access the original STDOUT, and thus the test harness sees the proper ok or not ok messages.

If you create temporary files like this, please note that your current directory is the same as the test script (even if you're running make test from the parent directory). Also, pick fairly safe cross-platform names if you want people to be able to use and test your module portably.

There is a better way to do this, though. The Test::Output module can handle this for us. This module gives us several functions that automatically take care of all of the details.

use strict;

use Test::More "noplan";
use Test::Output;

sub print_hello { print STDOUT "Welcome Aboard!\n" }
sub print_error { print STDERR "There's a hole in the ship!\n" }

stdout_is( \&print_hello, "Welcome Aboard\n" );

stderr_like( \&print_error, qr/ship/ );

All of the functions take a code reference as their first argument, but that's not a problem because we told you all about those in Chapter 7. If we don't have a subroutine to test, we wrap the code we want to test in a subroutine and use that.

sub test_this {

stdout_is( \&test_this, ... );

If our code is short enough, we might want to skip the step where we define a named subroutine and use an anonymous one.

stdout_is( sub { print "Welcome Aboard" }, "Welcome Aboard" );

We can even use an inline block of code, like we did with grep and map. As with those two list operators, notice that we don't have a comma after the inline code block.

stdout_is { print "Welcome Aboard" } "Welcome Aboard";

Besides Test::Output, we can do something similar with Test::Warn, which specifically tests warning output. Its interface uses the inline block form exclusively.


use Test::More "noplan";
use Test::Warn;

sub add_letters { "Skipper" + "Gilligan" }

warning_like { add_letters(  ) }, qr/non-numeric/;

We all strive to make our code warning-free, and we can test for that too. Perl warnings can change from version to version, and we want to know when the new warnings pop up, or if Perl will emit warnings on one of our customer's computers. The Test::NoWarnings module is a bit different from the ones we've already shown. It automatically adds a test just by loading the module, and we just have to ensure we add the hidden test to the count we give to Test::More.

use warnings;

use Test::More tests => 1;
use Test::NoWarnings;

my( $n, $m );
# let's use an uninitialized value
my $sum = $n + $m;

When we try to compute the sum, we use two variables to which we haven't given values. That triggers the annoying "use of uninitialized value" warning (ensure you have warnings turned on!). We don't want those sorts of things filling up our logfiles, now, do we? Test::NoWarnings tells us when that happens so we can fix it.

not ok 1 - no warnings
#   Failed test 'no warnings'
#   in /usr/local/lib/perl5/5.8.7/Test/NoWarnings.pm at line 45.
# There were 2 warning(s)
#       Previous test 0 ''
#       Use of uninitialized value in addition (+) at nowarnings.pl line 6.
# ----------
#       Previous test 0 ''
#       Use of uninitialized value in addition (+) at nowarnings.pl line 6.
# Looks like you failed 1 test of 1 run.

Table of Contents
© 2000- NIV