あけましておめでとうございます。
昨年は JS くらいしか書いてないような気がするので Perl +TDD で年始め。
題材には これであなたもテスト駆動開発マスター!?和田卓人さんがテスト駆動開発問題を解答コード使いながら解説します~現在時刻が関わるテストから、テスト容易性設計を学ぶ #tdd|CodeIQ MAGAZINE の問題を流用します。文量がありますが良記事なので、未読の方は一度読んでおくと良いです。(Perl環境は用意されているとします)
今回利用するモジュールとかツールとか
-
Test::Harness ... テストを実行するコマンド
prove
を利用できるようにする -
App::Cpanminus ... CPANモジュールをインストールするコマンド
cpanm
を利用できるようにする - Carton ... 開発するディストリビューションで依存するモジュールを管理するツール
- Minilla ... ディストリビューションの作成・管理する
- cpanfile ... モジュールの依存を記述するファイル
補助的なツール
-
Test::Pretty ...
prove
の出力した結果を見やすくしてくれます。 -
App::pfswatch ... 指定したディレクトリ(ファイル)が更新されたタイミングで、任意のコマンドを実行する
pfswatch
コマンドを使えるようにします。
準備する
モジュールの雛形を作る
普段使い慣れてるディストリビューション作成ツールを使えばいいんだと思いますが、ここでは Minilla を使います。
$ minil new Dest # Dest はディストリビューション名。適当な名前をつける
$ cd Dest
依存モジュールを cpanfile に追加する
minil new Dest
した時点で cpanfile ができているので、必要なモジュールを追加する
$ vi cpanfile
requires 'perl', '5.008001';
requires 'DateTime', '1.06';
on 'test' => sub {
requires 'Test::More', '0.98';
requires 'Test::MockDateTime', '0.02';
};
依存モジュールのインストール
$ cpanm --installdeps
若しくは
$ carton install
carton を使うと、ローカルディレクトリ内の local/lib 内に依存モジュールをインストールしてくれるので、モジュールのバージョン違いによる不具合を回避しやすいです
TDDのサイクルを回す
テストを書く
$ vi t/basic.t
テスト名は適当に
use strict;
use utf8;
# subtest 内で 日本語を扱う上手い方法よくわからなかったので
# とりあえずこうしてる。けど、ベストプラクティス知りたい。
BEGIN {
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";
}
use Test::More;
use Dest;
subtest 'newメソッドでインスタンスを作れるか' => sub {
my $dest = Dest->new;
ok $dest, '$dest = Dest->new';
};
done_testing;
最初のテストを実行
ここでは carton を利用しているものとします。
$ carton exec -- prove -lv
もしくは
$ carton exec -- prove -PPretty -lv # カラフルで見やすい
minilla が用意したプロダクトコードは最低限のコードしか用意されていないので、当然失敗する。
プロダクトコードを書く
テストが通るように仮実装したコードを書く。
$ vi lib/Dest.pm
package Greeter;
use 5.008005;
use strict;
use Carp;
our $VERSION = "0.01";
sub new {
my $class = shift;
bless +{} => $class;
};
1;
テストを実行
# cartonで依存モジュールを管理している場合
$ carton exec -- prove -lv
# cpanm (か cpan, cpanplusでモジュール)をインストールしている場合
$ prove -lv
t/00_compile.t ..
ok 1 - use Greeter;
1..1
ok
t/basic.t .......
# Subtest: newメソッドでインスタンスを作れるか
ok 1 - $dest = Dest->new
1..1
ok 1 - Subtest: newメソッドでインスタンスを作れるか
1..1
ok
All tests successful.
Files=2, Tests=2, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.31 cusr 0.03 csys = 0.38 CPU)
Result: PASS
テストを回す(むしろ、自働化する)
テストコード、プロダクトコードを書く毎に prove とかやってやってらんないので、コードが更新されたタイミングで自動的にテストを実行するようにする。
$ pfswatch . -e carton -- prove -lv
もしくは
$ pfswatch . -e carton -- prove -PPretty -lv
あとは、テスト書く -> テストが走る-> 失敗する -> テストが通るようにコードを書く -> テストが走る -> テストが通る -> テスト書く ... のサイクルを素早く回して開発をするだけです。
minil test する
開発のおわりには、git add
して minil test
しておわります。
参照
package Greeter;
use 5.008005;
use strict;
use Carp;
use DateTime;
our $VERSION = "0.01";
sub new {
my $class = shift;
bless {
time_zone => 'Asia/Tokyo',
greets => [ 'Good Morning', 'Good Afternoon', 'Good Evening' ],
}, $class;
};
sub greet {
my $self = shift;
my $now = DateTime->now( time_zone => $self->{time_zone} );
$self->{greets}[ $self->_get_index($now) ];
};
sub _get_index {
my $self = shift;
my $now = shift;
my $offset = ($now->hour * 60 + $now->minute);
($offset >= (60 * 18)) ? 2 :
($offset >= (60 * 12)) ? 1 :
($offset >= (60 * 5)) ? 0 : 2;
};
1;
use strict;
use utf8;
BEGIN {
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";
}
use Greeter;
use Test::More;
use Test::MockDateTime;
subtest 'Greeter->new で インスタンスの生成ができているか' => sub {
my $g = Greeter->new;
ok $g, 'my $g = Greeter->new';
ok $g->greet, '$g has "greet" method';
};
subtest '$g->greet で時刻に合わせた適切な挨拶を返すか' => sub {
my $g = Greeter->new;
my $ymd = '2013-01-01';
my $_050000 = "$ymd 05:00:00";
on $_050000 => sub {
is $g->greet, 'Good Morning', "$_050000 => Good Morning";
};
my $_115959 = "$ymd 11:59:59";
on $_115959 => sub {
is $g->greet, 'Good Morning', "$_115959 => Good Morning";
};
my $_120000 = "$ymd 12:00:00";
on $_120000 => sub {
is $g->greet, 'Good Afternoon', "$_120000 => Good Afternoon";
};
my $_180000 = "$ymd 18:00:00";
on $_180000 => sub {
is $g->greet, 'Good Evening', "$_180000 => Good Evening";
};
my $_045959 = "$ymd 04:59:59";
on $_045959 => sub {
is $g->greet, 'Good Evening', "$_045959 => Good Evening";
};
};
done_testing;