tsucchi’s diary(元はてなダイアリー)

はてなダイアリー(d.hatena.ne.jp/tsucchi1022)から移行したものです

STDIN/STDOUT を使うテスト

仕事メモ。

インタラクティブなコンソールアプリケーションだと、STDIN/STDOUT を使ったやりとりが発生します。こういうのをテストするときに、いままでは、

{
  local *STDOUT;
  open(STDOUT, '>', 'stdout.txt')
  do_something(); #STDOUT を使う何か
  close(STDOUT);
}

open(my $MY_STDOUT, '<', 'stdout.txt');
#... $MY_STDOUT の中身を読み出してテストする

みたいな処理を書いて、ファイルの中身でチェックしてました。(STDIN の場合は逆で、入力データをファイルに書いて、捻じ曲げた STDIN をアプリに読ませる)。

ファイルを噛ませるのが嫌だったのと、ちょっとトリッキーなのが気に入らなかったので、モジュールを使ってもうちょっと優雅に(?)書けないかな、と思い、思案してみました。

まずは STDIN。テストされ側はこんな感じ。

sub add {
  my $result = 0;
  while(<STDIN>) {
    chomp;
    $result += $_;
  }
  return $result;
}

STDIN に与えられた数字を延々足していきます。(数値以外の入力がくるとダメダメですが、説明用なのでそこは省略で)。

コレをテストするには、IO::ScalarArray を使うのが良さげです。こんな感じ。

#!/usr/bin/perl
use strict;
use warnings;
use Test::More "no_plan";
use IO::ScalarArray;


{
  local *STDIN;
  my @inputs = ("1\n", "2\n", "3\n");
  my $my_stdin = IO::ScalarArray->new(\@inputs);
  *STDIN = *$my_stdin;
  is(add(), 6); # 1+2+3 = 6
}

@inputs みたいな感じで配列を入力データにできます。一時ファイル不要。STDIN を捻じ曲げる必要があるのが若干ダサいですが、ファイルに割り当てるよりは良いんじゃないかな、と思います。

続いて、STDOUT。コレも同じようなサンプルでやってみましょう。こんな感じ。

sub add {
  my($a, $b) = @_;
  print $a + $b;
}

引数を足して、print します。「をいをい、何で print なんだよ、普通に戻り値返せよ」という突っ込みはなしで。説明用ですから。

テストは、IO::Capture::Stdout を使うのが良さげでした。(STDERR をつかまえるなら IO::Capture::Stderr)。こんな感じ。

#!/usr/bin/perl
use strict;
use warnings;
use Test::More "no_plan";
use IO::Capture::Stdout;

my $capture = IO::Capture::Stdout->new;
$capture->start;
add(1, 2);
$capture->stop;
is(join('', $capture->read), 3); # 1+2=3

キャプチャを stop しないと、read 出来ないことと、read は配列コンテキストじゃないと全行返さないっぽいので、join してあげるのがコツかな。

ちなみに、両方のモジュールを併用して、IO::ScalarArray で STDIN に書きつつ、IO::Capture::Stdout で STDOUT を読み取ることも、もちろん出来ます。(つーかそれが出来ないとインタラクティブなアプリのテストなんて無理だよね)

しかし Perl ってすげぇなぁ。本当に良くも悪くも何でもアリだなぁ。

追記(2009/01/25)

id:kits さんが、tie を使って STDIN/STDOUT を捕まえる方法を書いています。

tie ってなんか「禁断の領域」っぽくて、かっちょいいですね!きれいに書けるし、モジュール入れられない環境なら絶対これですね。

# ちなみに、さっきざっとソースを見たところ、IO::ScalarArray も IO::Capture も tie を使って同等の処理をしてるっぽい感じです。(つーか他にやり方を思いつかないから、当然といえば当然か。)

追記2(2009/02/25)

id:gfx さんに PerlIO の IO::scalar を使う方法を教えていただきました。
IO::scalarでIOのキャプチャ - use GFx::WebLog;

僕もちょっと書いてみました。 STDIN/STDOUT を使うテスト その2