Perl日記

日々の知ったことのメモなどです。Perlは最近やってないです。

Test::Spec入門

元ネタ「RSpec の入門とその一歩先へ - t-wadaの日記」第一イテレーション
PerlTest::Specだとどうなるかやってみた。

message_filter_spec.t

  • Perlのテストファイルの拡張子は*.t
  • RSpecっぽくするために、*_spec.tにする
  • とりあえずテストファイルをt/に置いてみる

t/message_filter_spec.t

+use Test::Spec; # use strict/warningsもされるらしい。へぇ
+                                                                                                                        
+describe MessageFilter => sub {
+};
+
+runtests unless caller;
  • 実行してみる
$ perl t/message_filter_spec.t 
no examples defined in spec package main at /Users/rightgo09/.perl5/perls/perl-5.16.2/lib/site_perl/5.16.2/Test/Spec/Context.pm line 273.
1..0
# No tests run!
  • RSpecでは「MessageFilterが見つからない」というエラーなのに対してTest::Specでは「実行するテストがない」というエラーになった

MessageFilter.pmを作成する

lib/MessageFilter.pm

+package MessageFilter {
+  use strict;
+  use warnings;
+}
+
+1;

t/message_filter_spec.t

+use MessageFilter;
use Test::Spec;
                                                                                                                        
describe MessageFilter => sub {
};

runtests unless caller;
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
no examples defined in spec package main at /Users/rightgo09/.perl5/perls/perl-5.16.2/lib/site_perl/5.16.2/Test/Spec/Context.pm line 273.
1..0
# No tests run!
  • まだ同じ

最初のテストを書く

t/message_filter_spec.t

use MessageFilter;
use Test::Spec;
                                                                                                                        
describe MessageFilter => sub {
+  it 'should detect message with NG word' => sub {                                                                      
+    my $filter = MessageFilter->new('foo');
+    ok($filter->is_detect('hello from foo'));
+  };
};

runtests unless caller;
  • shouldがないのが残念
  • あとPerlはメソッド名に「?」が使えない代わりに、booleanはis_xxxxがよく使われる
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
not ok 1 - MessageFilter should detect message with NG word
#   Failed test 'MessageFilter should detect message with NG word' by dying:
#     Can't locate object method "new" via package "MessageFilter"
#     at t/message_filter_spec.t line 6.
1..1
# Looks like you failed 1 test of 1.
  • 「newがありません」というエラー。さすがPerl

コンストラクタ作成

lib/MessageFilter.pm

package MessageFilter {
  use strict;
  use warnings;
+
+  sub new {
+    my ($class, $word) = @_;
+    bless { word => $word }, $class;
+  }
}

1;                                                                                                                      
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
not ok 1 - MessageFilter should detect message with NG word
#   Failed test 'MessageFilter should detect message with NG word' by dying:
#     Can't locate object method "is_detect" via package "MessageFilter"
#     at t/message_filter_spec.t line 7.
1..1
# Looks like you failed 1 test of 1.
  • 「is_detectがない」と怒られる

is_detectメソッド作成

  • 空のメソッドを作る

lib/MessageFilter.pm

package MessageFilter {
  use strict;
  use warnings;

  sub new {
    my ($class, $word) = @_;
    bless { word => $word }, $class;
  }
+
+  sub is_detect {
+  }
}

1;
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
not ok 1 - MessageFilter should detect message with NG word
#   Failed test 'MessageFilter should detect message with NG word'
#   at t/message_filter_spec.t line 7.
1..1
  • 「Failed test」だけだけど、ok()使っているだけなので、trueでない値が返ってきたようである

仮実装

lib/MessageFilter.pm

package MessageFilter {
  use strict;
  use warnings;

  sub new {
    my ($class, $word) = @_;
    bless { word => $word }, $class;
  }

  sub is_detect {
+    return 1;                                                                                                           
  }
}

1;
  • 一番trueだと分かりやすい1を返すようにする
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
ok 1 - MessageFilter should detect message with NG word
1..1
  • 通った! やったね!

三角測量

  • テストを増やす

t/message_filter_spec.t

use MessageFilter;
use Test::Spec;

describe MessageFilter => sub {
  it 'should detect message with NG word' => sub {
    my $filter = MessageFilter->new('foo');
    ok($filter->is_detect('hello from foo'));
  };
+  it 'should not detect message without NG word' => sub {
+    my $filter = MessageFilter->new('foo');
+    ok(!$filter->is_detect('hello, world'));
+  };                                                                                                                    
};

runtests unless caller;
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
ok 1 - MessageFilter should detect message with NG word
not ok 2 - MessageFilter should not detect message without NG word
#   Failed test 'MessageFilter should not detect message without NG word'
#   at t/message_filter_spec.t line 11.
1..2
# Looks like you failed 1 test of 2.
  • いい感じに失敗した

明白な実装

  • MessageFilter.pmに実装する

lib/MessageFilter.pm

package MessageFilter {
  use strict;
  use warnings;

  sub new {
    my ($class, $word) = @_;
    bless { word => $word }, $class;
  }

  sub is_detect {
-    return 1;                                                                                                           
+    my ($self, $word) = @_;
+    return index($word, $self->{word}) >= 0;                                                                            
  }
}

1;
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
ok 1 - MessageFilter should detect message with NG word
ok 2 - MessageFilter should not detect message without NG word
1..2
  • OK

beforeメソッドの抽出

t/message_filter_spec.t

 use MessageFilter;
 use Test::Spec;
 
 describe MessageFilter => sub {
+  my $filter;
+  before each => sub {
+    $filter = MessageFilter->new('foo');
+  };
+
   it 'should detect message with NG word' => sub {
-    my $filter = MessageFilter->new('foo');
     ok($filter->is_detect('hello from foo'));
   };
   it 'should not detect message without NG word' => sub {
-    my $filter = MessageFilter->new('foo');
     ok(!$filter->is_detect('hello, world'));
   };
 };
 
 runtests unless caller;
  • 変数スコープの関係上、beforeとitの外側で$filterを宣言しておく必要がある
  • あとはRSpecと同じでitの直前にbeforeが実行される
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
ok 1 - MessageFilter should detect message with NG word
ok 2 - MessageFilter should not detect message without NG word
1..2

eachは不要

  • Test::SpecもRSpecと当然同じ理念なので、beforeのデフォルトはeachである

t/message_filter_spec.t

 use MessageFilter;
 use Test::Spec;
 
 describe MessageFilter => sub {
   my $filter;
-  before each => sub {
+  before sub {
     $filter = MessageFilter->new('foo');
   };
 
   it 'should detect message with NG word' => sub {
     ok($filter->is_detect('hello from foo'));
   };
   it 'should not detect message without NG word' => sub {
     ok(!$filter->is_detect('hello, world'));
   };
 };
 
 runtests unless caller;
  • 実行してみる
$ perl -Ilib t/message_filter_spec.t 
ok 1 - MessageFilter should detect message with NG word
ok 2 - MessageFilter should not detect message without NG word
1..2

be_[predicate]マッチャ

  • そんなものはなーい

メッセージ

  • Test::Moreだと自力で最後の引数にテスト用のメッセージを書く必要があるけれど、Test::Specだと自動でいい感じに出してくれてる!

subject

  • ないなぁ…

というわけで、公式のRSpecと全く同じにはできないまでも、RSpecに慣れたひとならかなり見やすいテストになっているのではないでしょうか。
個人的には各テストのメッセージが勝手にくっついてくれるのは楽でいいなぁと思いました。


あとはモックとかshared_examplesとかshareとか試してみるつもり。