views:

279

answers:

3

I'm trying to use Test::More to unit test Perl functions that print to the screen.

I understand that this output may interfere with tools such as prove.

How can I capture this output so I can print it with diag(), and also run tests on the output itself?

+12  A: 

UPDATE: IMHO, the correct answer to this question ought to be to use Test::Output:

#!/usr/bin/perl

use strict; use warnings;

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

sub myfunc { print "This is a test\n" }

stdout_is(\&myfunc, "This is a test\n", 'myfunc() returns test output');

Output:

C:\Temp> tm
1..1
ok 1 - myfunc() returns test output

I am leaving the original answer for reference as, I believe, it still illustrates a useful technique.

You can localize STDOUT and reopen to a scalar before calling the function, restore afterward:

#!/usr/bin/perl

use strict; use warnings;

use Test::More tests => 1;

sub myfunc { print "This is a test\n" }

sub invoke {
    my $sub = shift;
    my $stdout;
    {
        local *STDOUT;
        open STDOUT, '>', \$stdout
            or die "Cannot open STDOUT to a scalar: $!";
        $sub->(@_);
        close STDOUT
            or die "Cannot close redirected STDOUT: $!";
    }
    return $stdout;
}

chomp(my $ret =  invoke(\&myfunc));

ok($ret eq "This is a test", "myfunc() prints test string" );
diag("myfunc() printed '$ret'");

Output:

C:\Temp> tm
1..1
ok 1 - myfunc() prints test string
# myfunc() printed 'This is a test'

For versions of perl older than 5.8, you probably need to use IO::Scalar, but I do not know much about how things worked before 5.8.

Sinan Ünür
Just when I think I'm wasting too much time on SO, I learn something cool! Thanks.
FM
Whoever downvoted this answer needs to explain his/her reasoning.
Sinan Ünür
Well, technically they don't, but it sure is nice when they do. :p (+1, that's an awesome module)
Robert P
@Robert P: Thank you. The answer was correct before I added the information about `Test::Output` which is why, **morally** the drive-by downvoter should have explained his/her reasoning.
Sinan Ünür
I didn't downvote it, but I think the orginal answer does too much work to get the job done. It's too complicated. I'm a bit biased toward Test::Output though, but I use it as a last resort.
brian d foy
+1 for usefulness. Very few of my numerous libraries ever printed anything worth testing on the output so I never encountered this one before.
DVK
+4  A: 

I'd look at letting a module handle this for you. Look at Capture::Tiny.

mpeters
+1 I did not know about Capture::Tiny.
Sinan Ünür
+2  A: 

If this is code that you are writing yourself, change it so that the print statements don't use a default filehandle. Instead, give yourself a way to set the output filehandle to anything you like:

sub my_print {
     my $self = shift;
     my $fh = $self->_get_output_fh;
     print { $fh } @_;
     }

sub _get_output_fh { $_[0]->{_output}  || \*STDOUT }
sub _set_output_fh { $_[0]->{_output} = $_[1] } # add validation yourself

When you test, you can call _set_output_fh to give it your testing filehandle (perhaps even an IO::Null handle). When another person wants to use your code but capture the output, they don't have to bend over backward to do it because they can supply their own filehandle.

When you find a part of your code that is hard to test or that you have to jump through hoops to work with, you probably have a bad design. I'm still amazed at how testing code makes these things apparent, because I often wouldn't think about them. If it's hard to test, make it easy to test. You generally win if you do that.

brian d foy
Amen. Writing unit tests (or planning for them if you do TDD, whether you know it or not) very often results in marked design improvements.
DVK