Perl入学式2013年第5回より 〜練習問題の最終問題:後編〜

昨日の続きです。
昨日の記事はこちら。
Perl入学式2013年第5回より 〜練習問題の最終問題(1)〜 - draft

今日は以下の問題から、
workshop-2013-05/slide.md at master · perl-entrance-org/workshop-2013-05 · GitHub

最終問題

  • 隣の人とペアを作ってぶつかり稽古(ペアプログラミング)をしましょう. 1台のPCで作業を行います
  • 以下の機能があるYAPCモジュールを実装してください
    • 来年のYAPC::Asiaは2014年8月28日から30日に開催予定です. 日付を教えてくれるモジュールを2人で作りましょう
    • YAPC::year()で年を4桁の整数で返します(テストをAの人が, コードをBの人が書きましょう)
    • YAPC::month()で月を2桁の整数で返します(テストをBの人が, コードをAの人が書きましょう)
    • YAPC::day()で日付を2桁の整数で返します(テストをAの人が, コードをBの人が書きましょう)
    • YAPC::is_yet()で開催前か開催後かを真か偽で返します(テストをBの人が, コードをAの人が書きましょう)
  • 最後の項目はテストもコードも結構難しいです! サポーターの人に「どうすればXXXXが出来る?」と聞きましょう

最後の問題「YAPC::is_yet()」にぶつかってみたいと思いますが、まずは昨日までの(その一つ前の問題までの)おさらいとして、回答はこんな感じでした。

package YAPC;
use 5.008005;
use strict;
use warnings;

our $VERSION = "0.01";

sub year {
    return 2014;
}
sub month {
    return 8;
}
sub day {
    return 28;
}

1;
#!/usr/bin/env perl
use strict;
use warnings;
use YAPC;

print "Year: ".YAPC::year()."\n";
print "Month: ".YAPC::month()."\n";
print "Day: ".YAPC::day()."\n";

実行すると、こんな。

Year: 2014
Month: 8
Day: 28

はい。んで、本題のこちらですが、

  • YAPC::is_yet()で開催前か開催後かを真か偽で返します(テストをBの人が, コードをAの人が書きましょう)

まずそのー、「開催前か開催後かを真か偽で返します」というのが、どういう結果になればいいのか、というのがよく分からなかったんですが・・

その少し後のヒントなどを見ますと、

本当に開催後にis_yet()が真になるか調べたいかと思います

という表現がありましたので、ええと、これはようは「開催後=真」で、「開催前=偽」という結果になればいいのかなと。そう仮定して進めました。

あと、同じく分からなかったのが、そもそも「真偽」ってなんだっけ、ということですね。
これまでも何度か出てきた表現ではあって、まあ何となーく、わかったようなスルーするような感じで受け流していましたが、いよいよもう少し、突っ込んで理解しておかないといかんのかなあ・・と。

まあ、でもとりあえずはそのようなウネウネした感触のまま、このような感じで作ってみました。

(略)

sub is_yet {
    my $str = shift;
    if ($str =~ /(\d\d\d\d)\/(\d\d?)\/(\d\d?)/) {
        if ($1 > 2014) {
            return 'true';
        } elsif ($1 == 2014 && $2 > 8) {
            return 'true';
        } elsif ($1 == 2014 && $2 == 8 && $3 > 27) {
            return 'true';
        } else {
            return 'false';
        }
    }
}

1;
(略)

print "True or False: ".YAPC::is_yet('2010/2/27')."\n";

実行!

True or False: false

はい。ええと実際には、上記のモジュールになるまで、if文の整理にだいーぶ時間がかかったりしたのですが、その辺の試行錯誤は割愛します。(これまでの記事のそれと似たり寄ったりなので・・笑)

結果を見た感じ、なんだか良さそうです。
じつは「YAPC::is_yet()」の()内に引数を、それも文字列で入れないといけなそうだぞ、とかは上記の割愛した試行錯誤の中でいろいろやった末に出てきたところですが、それもいつも通りの感じなので、おぼろげにご想像頂ければ、と。

あと、日付を半角スラッシュで入れていて、それに対応してif文内の正規表現を作っていますが、この辺はもっと工夫はできるかなとは思っています。半角ダッシュ( - ←コレ)でつなげても、スラッシュでつなげてもどちらでも反応できるようにする、とか。
あるいはそもそも、ダイアログ型にしてSTDINで打ち込んでもらうように仕掛けて、その際に書式を知らせるなり、エラー文で知らせるなり。

まあでも、たぶん今回はその辺は本題でも無さそうなので、一旦最低限の回答として、上記にしてみました。

で、ああ終わった、大変だったけど結構すんなり出来たかなー、と思いつつ、そういえばコレ、一人二役のぶつかり稽古だった、テストやらないと。
ということで、資料に沿って、このような感じで作ってみました。
#これまでの回答に対するテストも含めています。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use YAPC;

is YAPC::year(), 2014;
is YAPC::month(), 8;
is YAPC::day(), 28;
ok YAPC::is_yet('2014/12/27');
ok !YAPC::is_yet('2013/12/27');

done_testing();

はい。最後の方、「is_yet」のテストでは、真を確かめる式(開催後の日付)と、偽を確かめる式(開催前の日付)を入れています。

緊張しつつ、ターミナルにこんな感じで打ち込んで、

prove -Ilib YAPC.t

実行!!

YAPC.t .. 1/?
#   Failed test at YAPC.t line 11.
# Looks like you failed 1 test of 5.
YAPC.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/5 subtests

Test Summary Report
-------------------
YAPC.t (Wstat: 256 Tests: 5 Failed: 1)
  Failed test:  5
  Non-zero exit status: 1
Files=1, Tests=5,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.02 cusr  0.01 csys =  0.05 CPU)
Result: FAIL

ん、ん〜〜・・ま、最後の行に思いきり「FAIL」書いてあるので、ダメなんでしょうね。
たぶん、じーっと読んでみると、「5個テストあるけど1個コケてるよ」という意味なんだと思われました。

明らかに怪しいのは、最後の2つなので、まず最後の2つをエスケープして、もう一回テストします。
実行!!

YAPC.t .. ok
All tests successful.
Files=1, Tests=3,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.01 cusr  0.01 csys =  0.04 CPU)
Result: PASS

おおお〜〜! OKっぽいですね。はい。
んで、じゃあ残りを一個ずつ確かめますと、まず真を返すはずの方を生かして・・実行!

YAPC.t .. ok
All tests successful.
Files=1, Tests=4,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.02 cusr  0.01 csys =  0.05 CPU)
Result: PASS

おお! やった。testが4個になって、それでもPASSってなってます。
かなり時間かかったif文だったので、OKだとありがたいです・・。

じゃ、やっぱり犯人は最後のやつですか・・念のためもう一回実行しましたが、やはり上記と同じFAILが出てきました。

仕方ないので(というか)、もう一回モジュールの方を見直します。

(略)
sub is_yet {
    my $str = shift;
    if ($str =~ /(\d\d\d\d)\/(\d\d?)\/(\d\d?)/) {
        if ($1 > 2014) {
            return 'true';
        } elsif ($1 == 2014 && $2 > 8) {
            return 'true';
        } elsif ($1 == 2014 && $2 == 8 && $3 > 27) {
            return 'true';
        } else {
            return 'false';
        }
    }
}

1;

これ、突き詰めて考えると、どう考えても最後の

return 'false';

が問題なんですよね・・で、ふと思ったのが、上記の疑問「そもそも真偽ってなんだっけ?」でありまして・・。

ようは、上の回答では思いっきり文字列で「'false'」って書いてますが、それは読み取る人間だけが「偽」だと認識できるのであって、Perlさんがそう認識してるとは限らないのか・・? と思いまして。

んで、検索しましたら、以下の記事に当たりまして、
http://d.hatena.ne.jp/perlcodesample/20080323/1206280262
http://d.hatena.ne.jp/perlcodesample/20100126/1264257759

いずれもPerl入門ではおなじみの木本さんの記事ですね。
いつもありがとうございます。

話を戻しますと、こちらを拝見するに、ようは「偽」として認識してもらえる値はほんの数種類だけで、それ以外は全部「真」であると。
ということは、いくら「'false'だよ!!」ゆっても「真」として認識されてるってことかな、と思いまして、モジュールの該当部分を以下のようにしてみました。

return 0;

ここでもまた、「undef」をシングルクォーテーションで囲ってみたり(もちろん駄目)、いろいろ試したんですが、最終的にこれにしたところ、テストの結果がこんな!

YAPC.t .. ok
All tests successful.
Files=1, Tests=5,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.01 cusr  0.01 csys =  0.04 CPU)
Result: PASS

おおお〜〜!! ということで。ついにPASSしましたよ〜〜、よかった〜、と思って、あらためてウットリするために、処理のプログラムも実行してみます。

Year: 2014
Month: 8
Day: 28
True or False: 0

ん〜、ま、大きな問題ではないかもしれませんが、最後の「True or False: 0」というのはちょっと意味不明なので、「False」の後に補足でゼロを入れてみたり・・

#!/usr/bin/env perl
use strict;
use warnings;
use YAPC;

print "Year: ".YAPC::year()."\n";
print "Month: ".YAPC::month()."\n";
print "Day: ".YAPC::day()."\n";
print "True or False(0): ".YAPC::is_yet('2010/2/27')."\n";

実行!

Year: 2014
Month: 8
Day: 28
True or False(0): 0

なんだか泥縄ですが・・まあ、自分では納得しました!

というところで、一旦区切りというか、教えて頂いたところは最低限さらったと思うのですが、問題文ではこんなヒントもあったりして・・

  • 日付をうまく操作するにはTime::Pieceというモジュールを使います

もっとテストしたい方は時間を進めて本当に開催後にis_yet()が真になるか調べたいかと思います

    • テスト内の時間を操作するにはTest::MockTimeを使ってみてください

ちょっとこの辺の話、ぜんぜんまた理解できてないので、継続して考えてみたいと思います。

で、終わろうと思っていたんですが、じつはこの流れでまだひとつ、気になってるところがあって。
最後にそれやって終わろうと思います。

具体的には、Minillaで作ったYAPCディレクトリの中にはtディレクトリっていう、たぶんテストファイルを入れるためなのであろうディレクトリがあって。にもかかわらず、上記のテストはそこから出して、YAPCディレクトリの中で動かしているので、これ普通にtディレクトリの中に置いて動かしたいなと。

なんですが、実際にそうやってみると、たとえばtディレクトリの中で

prove -Ilib YAPC.t

あるいは、

prove -l YAPC.t

と打ち込むと・・

(略)
BEGIN failed--compilation aborted at YAPC.t line 5.
YAPC.t .. Dubious, test returned 2 (wstat 512, 0x200)
No subtests run

Test Summary Report
-------------------
YAPC.t (Wstat: 512 Tests: 0 Failed: 0)
  Non-zero exit status: 2
  Parse errors: No plan found in TAP output
Files=1, Tests=0,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.02 CPU)
Result: FAIL

なんてこと言われたり、それならってことで、そこから自分だけ抜けて、YAPCディレクトリの中で同様にすると今度は

Cannot detect source of 'YAPC.t'! at (以下略・相当長いエラーメッセージ)

なんて風にワケわからない境地に追い込まれたりして・・これ、tディレクトリって一体何なんや・・と思ったりもしていて。

んで、でもそのまま、というのも気持ち悪いので、ああそうだ、tokuhiromさんのブログあらためて読み返そう、ということでこちらを見ますと、
Minilla を用いた Perl モジュールの作り方 - blog.64p.org

下の方にこんな記述がありまして。

テストをうごかす
% minil test とすると、テストスイートがうごきます。
% prove -rl t/ などとしても、シンプルな pure perl のモジュールの場合には問題ありません。

おお、「minil test」! それ『雅なPerl入門』にも書いてあったやつや! さっそくやってみよ!

と思って、やったんですがなんか・・最後に

(略)
All tests successful.

とは出てくるものの、ちょっと違う気がする・・と思って、試しにYAPC.pmをわざとミスらせてみたら(最後の「0」を「'false'」に戻した)、

(略)
All tests successful.

ってやっぱり同じ結果が出てきた・・あかんやん。

んむ・・やはりMinilla俺には敷居高かったな・・と思ったんだけど、上記の説明の下の方に書かれてる「prove -rl t/」もせっかくなのでやっておくか・・と思ってやったら、

t/00_compile.t .. ok
t/YAPC.t ........ ok
All tests successful.
Files=2, Tests=6,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.03 cusr  0.01 csys =  0.06 CPU)
Result: PASS

おおお(笑)、これは明らかに「YAPC.t」通ってる。
ここでも念のため、わざとYAPC.pm間違えさせてみたら、ちゃんとコケるし!

t/YAPC.t ........ Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/5 subtests

Test Summary Report
-------------------
t/YAPC.t      (Wstat: 256 Tests: 5 Failed: 1)
  Failed test:  5
  Non-zero exit status: 1
Files=2, Tests=6,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.03 cusr  0.01 csys =  0.06 CPU)
Result: FAIL

テストがコケて嬉しいなんて(笑)。

ということで、とりあえず現状の自分のあり方だと、Minillaでもこの方法でテスト作って/使っていけそうです。

というか、ここまでやってようやく『雅なPerl入門』でも紹介されていたtokuhiromさんのKindle本『Perl Testing Book』を読む最低限の下地が出来てきたかなっていう・・そんな印象も持ちつつありますが。

いやあ・・いつもに増してグダグダな進行でしたが、いろいろ課題も残しつつ、とりあえず一人ぶつかり稽古もあらかた終わりました。

さて次回ですが、上記で少し触れたままのTime系モジュールを予習してみるか、あるいはそれは1月の補講で直接見聞きすることにして、最近自分で作ってるスクリプトにまつわる気づきアレコレに行くか、という感じでしょうか。

それにしてもプログラミング、面白いですね・・キーワードは再現性、ですかね。再現してるポイントを探すゲームというか・・再現するはずなのにしない、とかいう状況になるとほんとワクワクします。こんな感じで楽しんでいけたらよいですが・・

追記:上記をUPして、githubにもまとめた後、何となくもう一回

$ minil test

やったら、

(略)
t/00_compile.t .. ok
t/YAPC.t ........ ok
All tests successful.
Files=2, Tests=6,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.03 cusr  0.01 csys =  0.07 CPU)
Result: PASS

って風にtディレクトリ内の「YAPC.t」も通ったっぽい。わからんことだらけや・・