標準出力に対してテストをする

# 標準出力のテストをする

みなさん！perl書いてますか？

今回は、標準出力に対してテスト方法をご紹介します。

## 使用モジュール

テストで使用するモジュールといえば、 `Test::More` です！ (標準モジュール)

* Test::More - yet another framework for writing test scripts - metacpan.org
 * https://metacpan.org/pod/Test::More

使い方などについては、下記のサイトがわかりやすいです。

* Test::More - テストを書くためのもう一つのフレームワーク - perldoc.jp
 * http://perldoc.jp/docs/modules/Test-Simple-0.99/lib/Test/More.pod

* Test::More - 自動テストのためのテストプログラムを書く - Perl入門ゼミ
 * https://tutorial.perlzemi.com/blog/20090221123410.html

ということで使い方の説明は、省略しますです。

テストのとき、
関数からの戻り値は、簡単にテストできるのですが、
標準出力される文字(print)などは、テストしにくいですよね・・・。

そこで今回ご紹介したいのが `Test::Trap` というモジュールです。

* Test::Trap - Trap exit codes, exceptions, output, etc. - metacpan.org
 * https://metacpan.org/pod/Test::Trap

標準モジュールではないので、いつものコマンドでまずはインストールします。

`$ cpanm -v Test::Trap`

このモジュールを簡単に説明すると・・・

* 実行したコードの標準出力などが簡単に取得できる
* プログラムの終了コード・標準エラー出力なども取得可能

## 試してみましょう

例として、以下の3パターンのテストしてみます。

* プログラムの終了ステータス
* 標準出力
* 標準エラー出力

下記の`Target`モジュールに対して、テストをしていきます。

```perl:Target.pm
package Target;
use Object::Simple -base;

sub exit_ok {
    my $self = shift;

    exit 0; # 正常終了
}

sub exit_error {
    my $self = shift;

    exit 1; # 異常終了
}

sub print_hoge_stdout {
    my $self = shift;

    print "hoge!"; # 標準出力
}

sub print_fuga_stderr {
    my $self = shift;

    print STDERR "fuga!"; # 標準エラー出力
}

sub print_piyo_warn {
    my $self = shift;

    warn "piyo!"; # 標準エラー出力
}

sub die_foo {
    my $self = shift;

    die "foo"; # 標準エラー出力
}

1;
```

### 使い方

`trap {(実行コード)}`

trap内の`実行コード`が処理されると、`$trap`に結果が入ります。
あとは、取得したい出力などをTest::Trapの関数を実行して取得します。

```perl
use Test::More;
use Test::Trap;

trap {(実行コード)};

is $trap->(Test::Trapの関数), (期待値), "(テスト名)";

done_testing;
```

ということで、実際に試してみます。

### テスト: プログラムの終了ステータス

プログラムの終了ステータスを取得したいときは、`$trap->exit`を使用します。

```perl:テスト対象(Target.pm)
sub exit_ok {
    my $self = shift;

    exit 0; # 正常終了
}

sub exit_error {
    my $self = shift;

    exit 1; # 異常終了
}
```

```perl:テスト
use strict;
use warnings;
use utf8;
use Test::More;
use Test::Trap;
use Target;

my $target = Target->new;

# 正常終了
trap { $target->exit_ok };
is $trap->exit, 0, "exit_ok";

# 異常終了
trap { $target->exit_error };
is $trap->exit, 1, "exit_error";

done_testing;
```

```shell:テスト結果
ok 1 - exit_ok
ok 2 - exit_error
1..2
```

すべてのテストが成功しました。

ちなみに間違っていると・・・

```perl:テスト
use strict;
use warnings;
use utf8;
use Test::More;
use Test::Trap;
use Target;

my $target = Target->new;

# 正常終了
trap { $target->exit_ok };
is $trap->exit, 0, "exit_ok";

# 異常終了
trap { $target->exit_error };
is $trap->exit, 0, "exit_error"; # NG!!

done_testing;
```

```shell:テスト結果
ok 1 - exit_ok
not ok 2 - exit_error
#   Failed test 'exit_error'
#   at hoge.t line 16.
#          got: '1'
#     expected: '0'
1..2
# Looks like you failed 1 test of 2.
```

上記の例だと「コードの実行結果が`1`で期待値は`0`でした」と表示されてテストが失敗します。

### テスト: 標準出力

標準出力を取得したいときは、`$trap->stdout`を使用します。

```perl:テスト対象(Target.pm)
sub print_hoge_stdout {
    my $self = shift;

    print "hoge!"; # 標準出力
}
```

```perl:テスト
use strict;
use warnings;
use utf8;
use Test::More;
use Test::Trap;
use Target;

my $target = Target->new;

trap { $target->print_hoge_stdout };
is $trap->stdout, "hoge!", "print_hoge_stdout";

done_testing;
```

```shell:テスト結果
ok 1 - print_hoge_stdout
1..1
```

### テスト: 標準エラー出力

標準エラー出力を取得したいときは、`$trap->stderr`を使用します。

```perl:テスト対象(Target.pm)
sub print_fuga_stderr {
    my $self = shift;

    print STDERR "fuga!"; # 標準エラー出力
}
```

```perl:テスト
use strict;
use warnings;
use utf8;
use Test::More;
use Test::Trap;
use Target;

my $target = Target->new;

trap { $target->print_fuga_stderr };
is $trap->stderr, "fuga!", "print_fuga_stderr";

done_testing;
```

```shell:テスト結果
ok 1 - print_fuga_stderr
1..1
```

#### warnの場合

```perl:テスト対象(Target.pm)
sub print_piyo_warn {
    my $self = shift;

    warn "piyo!"; # 標準エラー出力
}
```

warnが実行されると、`piyo! at Target.pm line 31.`というようなメッセージが標準エラー出力にでます。

指定した文字列の後ろにファイル名や行数が追加されてきてしまい、固定的な文字(完全に一致)ではテストしにくそうです。
※行数が変更になったり、ファイルがリネームされたりしたらテストが失敗してしまうようになる

そんなときは、Test::Moreの`like`が便利です。
正規表現で期待値を設定できます。

```perl:テスト
use strict;
use warnings;
use utf8;
use Test::More;
use Test::Trap;
use Target;

my $target = Target->new;

trap { $target->print_piyo_warn };
like $trap->stderr, qr/^piyo!/, "print_piyo_warn";

done_testing;
```

```shell:テスト結果
ok 1 - print_piyo_warn
1..1
```

#### dieの場合

dieのときは、Test::Trapの`:die`を明示的にインポートして`$trap->die`を使います。

```perl:テスト対象(Target.pm)
sub die_foo {
    my $self = shift;

    die "foo"; # 標準エラー出力
}
```

dieもwarnと同じく実行された行の情報が後ろにつくので`like`でテストを書いておきます。

```perl:テスト
use strict;
use warnings;
use utf8;
use Test::More;
use Test::Trap qw/:die/;
use Target;

my $target = Target->new;

trap { $target->die_foo };
like $trap->die, qr/^foo/, "die_foo";

done_testing;
```

```shell:テスト結果
ok 1 - die_foo
1..1
```

## まとめ

このように`Test::Trap`を使用すると、標準出力などが簡単にテストできます。

Test::Trapのページを見ると他にもできることがありそうなので、試してみてくださいね！

* Test::Trap - Trap exit codes, exceptions, output, etc. - metacpan.org
 * https://metacpan.org/pod/Test::Trap