How can I unit test Perl functions that print to the screen?

后端 未结 3 505
广开言路
广开言路 2020-12-28 13:31

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

相关标签:
3条回答
  • 2020-12-28 13:35

    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.

    0 讨论(0)
  • 2020-12-28 13:53

    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.

    0 讨论(0)
  • 2020-12-28 13:58

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

    0 讨论(0)
提交回复
热议问题