Perl で、あるコードが標準エラー出力に吐き出した内容をテストしたい場面がありました。
自分でまず思いついたのは STDERR を dup して保存しておいて、ファイルにリダイレクトして、元に戻して、というやりかた。これはこれで動くのですが面倒。こういう場合は Test::Output (や miyagawa さんに教えてもらった Capture::Tiny) が便利です。
Test::Output はこんな感じ。 std(out|err)_(is|isnt|like) といったテスト関数が使えるようになります。
use Test::Output; use Test::More; stderr_is { # STDERR になにか出力するコード } "STDERRの内容", "description"; stdout_like { # code } qr/regexp/, "description";
Capture::Tiny を使うと
use Capture::Tiny qw/ capture /; my ($stdout, $strerr) = capture { # code };
このようにして出力を捕まえることができます。
Capture::Tiny の SEE ALSO には、「出力を捕まえるモジュールはCPANにたくさんあるけど、いまいち用途が限定的なので作ったよ」(意訳)というようなことが書いてありました。汎用的に STDOUT, STDERR を捕まえるのには Capture::Tiny が良さげですね。
[追記]
Capture::Tiny は子プロセスの出力も捕まえられますが、Test::Output ではそれはできないようです。
use strict; use Capture::Tiny qw/ capture /; use Test::More; use Test::Output; my %Tests = ( internal => sub { print "FOO" }, subprocess => sub { system("echo", "-n", "FOO") }, ); my @Captures = ( sub { my ($type, $code) = @_; my ($out) = capture { $code->() }; is $out, "FOO", "Capture::Tiny $type"; }, sub { my ($type, $code) = @_; stdout_is { $code->() } "FOO", "Test::Output $type"; }, sub { my ($type, $code) = @_; my $cap; open my $out, ">", \$cap; local *STDOUT = $out; $code->(); is $cap, "FOO", "PerlIO $type"; }, ); for my $capture ( @Captures ) { for my $type ( keys %Tests ) { $capture->( $type, $Tests{$type} ); } } done_testing;
Perl で print "FOO" するのと、system で echo -n FOO するのを、それぞれ Capture::Tiny, Test::Output, PerlIO で捕まえようとしてみます。
実行結果はこうなりました。
ok 1 - Capture::Tiny subprocess ok 2 - Capture::Tiny internal FOOnot ok 3 - Test::Output subprocess # Failed test 'Test::Output subprocess' # at cap.pl line 18. # STDOUT is: # # not: # FOO # as expected ok 4 - Test::Output internal FOOnot ok 5 - PerlIO subprocess # Failed test 'PerlIO subprocess' # at cap.pl line 26. # got: undef # expected: 'FOO' ok 6 - PerlIO internal 1..6 # Looks like you failed 2 tests of 6.
Test::Output, PerlIO だと子プロセスの出力を捕まえることができずに、失敗しています。