日向夏特殊応援部隊

俺様向けメモ

Test::Mock::Guard Released

さっき nekokak さんと xaicron さんにそそのかされて Test::Mock::Guard ってモジュールを書いてみました。

そもそも Perl には Test::MockObject と言う汎用の Mock モジュールがあるんですけど、あれこれ余計な機能がたくさんついてたり Mock 化すると多分元に戻せないと言うのがあってもっとシンプルな奴がほしいなと思って作ってみた次第です。SYSNOPSIS のコピペですけど、

use Test::More;
use Test::Mock::Guard qw(mock_guard);

package Some::Class;

sub new { bless {} => shift }
sub foo { "foo" }
sub bar { 1; }

package main;

{
  ### このスコープでは Mock 化されてる
  my $guard = mock_guard( 'Some::Class', +{ foo => sub { "bar" }, bar => 10 } );
  my $obj = Some::Class->new;
  is( $obj->foo, "bar" );
  is( $obj->bar, 10 );
}

### ブロックを抜けると元に戻る
my $obj = Some::Class->new;
is( $obj->foo, "foo" );
is( $obj->bar, 1 );

done_testing;

って感じに書く事が出来ます。下位モジュールがよしなにテストされているとして、それを呼び出すモジュールのテストなんかを書く場合、意図的に下位モジュールの挙動をコントロールしながら書きたいケースがあると思います。

例えば、

package AKB48::Fun;

sub buy_dvd {
  ### 何か色々やる
}

package Hidek;

sub purge_stress {
  AKD48::Fun->buy_dvd;
}

的な感じになっていて、AKB48::Fun モジュールは十分テストされているとすれば、

subtest "buy dvd is success" => sub {
  my $mock_guard = mock_guard( 'AKB48::Fun', +{
    buy_dvd => sub {
      return +{ title => "【特典生写真付き】ここにいたこと(初回限定盤)", price => 3500 },
    }
  } );

  ### テスト書く
};

subtest "buy dvd is failed" => sub {
  my $mock_guard = mock_guard( 'AKB48::Fun', +{
    buy_dvd => sub {
      croak "現金が足りません!カードで支払って下さい";
    },
  } );

  ### テスト書く
};

みたいな感じの事が出来ますよと。

使いどころを適切に選べば Mock を使ったテストはとても有効だと思うのでぜひお試し下さいませ。かしこ。