sironekotoroの日記

Perl と Mac の初心者の備忘録

Perlモジュール作成実況中継 一気通貫 テストを添えて

Perl でモジュール作るなら minilla がおすすめ(というかそれしか知らない)

Perl でモジュールを作るときは Minilla を使っています。

きっかけは Web+DB Press に掲載されて記事でした。

gihyo.jp

体系だった記事があると大変助かります。

$ cpanm Minilla でインストール後、早速モジュール作ります。

作るのは Acme::MetaVar というモジュール。

このモジュールは use Acme::MetaVar すると、hoge というメソッドを呼び出して使うことができる・・・とします。

利用イメージとしてはこんな感じ。

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

use Acme::MetaVar;

my $am = Acme::MetaVar->new();
print $am->hoge() . "\n";   # hoge

minil の使い方については記事を見てもらうとして、こんな感じで進んでいきます。

$ minil new Acme::MetaVar
(中略)
Finished to create Acme::MetaVar

これで雛形が出来上がったので、早速モジュール本体を作っていきます・・・の前に、git でコミットしておきます。

$ cd Acme-MetaVar/
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 8704b2c] initial commit
 11 files changed, 585 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .travis.yml
 create mode 100644 Build.PL
 create mode 100644 Changes
 create mode 100644 LICENSE
 create mode 100644 META.json
 create mode 100644 README.md
 create mode 100644 cpanfile
 create mode 100644 lib/Acme/MetaVar.pm
 create mode 100644 minil.toml
 create mode 100644 t/00_compile.t

ここまでの進捗です。

github.com

いざモジュール作成

モジュール作成に着手します。

$ cd Acme-MetaVar/
$ vim lib/Acme/MetaVar.pm

雛形のコードにサブルーチン9行足しただけですが、コードを載せておきます。

package Acme::MetaVar;
use 5.008001;
use strict;
use warnings;

our $VERSION = "0.01";

sub new {
    my $class = shift;
    my $self = bless {}, $class;
    return $self;
}

sub hoge {
    return "hoge";
}

1;
__END__
(中略)

さて、出来上がったら -c オプションつけてまずは文法チェックしてみます。

$ perl -c lib/Acme/MetaVar.pm
lib/Acme/MetaVar.pm syntax OK

問題ないっすね。

ここまでの進捗です

github.com

テストしてみる

ここで、Minilla が用意した雛形のテストを実行します。モジュールが use できるかどうか?というテストです。

テストには Perl と一緒にインストールされる prove コマンドを用います。

この provet/ ディレクトリ内のテストスクリプトを連続で実行してくれます。

実行場所は t/ ディレクトリと同じ階層になるので注意。t/ ディレクトリの中ではないです。

引数の -lハイフン エル を忘れずに。

$ prove -l
t/00_compile.t .. ok
All tests successful.
Files=1, Tests=1,  1 wallclock secs ( 0.04 usr  0.01 sys +  0.14 cusr  0.03 csys =  0.22 CPU)
Result: PASS

Result: PASS 、最高ですね。テスト成功です。

-lハイフン エル オプションをつけないと、「Perlがモジュール見つけられないよ!」ってな警告と共にテストが失敗します。

Perl がモジュールを探す場所(パス)に Acme::MetaVar が入っていないので当然ですね。今作ってる最中でインストール前だし・・・

以下がテスト失敗時のメッセージです。

$ prove
t/00_compile.t .. 1/?
#   Failed test 'use Acme::MetaVar;'
#   at t/00_compile.t line 4.
#     Tried to use 'Acme::MetaVar'.
#     Error:  Can't locate Acme/MetaVar.pm in @INC (you may need to install the Acme::MetaVar module)
(中略・ここにPerlがモジュールを探したパスが列挙されている)
at t/00_compile.t line 4.
# BEGIN failed--compilation aborted at t/00_compile.t line 4.
# Looks like you failed 1 test of 1.
t/00_compile.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests

Test Summary Report
-------------------
t/00_compile.t (Wstat: 256 Tests: 1 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
Files=1, Tests=1,  1 wallclock secs ( 0.03 usr  0.01 sys +  0.14 cusr  0.04 csys =  0.22 CPU)
Result: FAIL

ではここで一旦 git add . して git commitです。

sironekotoro 20-09-06 14:38:39 ~/Dropbox/perl/temp/Acme-MetaVar  (master +)
$ git commit -m "add method  'new',  'hoge'"
[master 79c4048] add method  'new',  'hoge'
 1 file changed, 9 insertions(+), 1 deletion(-)

閑話休題、実行時のパスについて

さて、スクリプトから開発中のモジュールを use して使って試してみたいときにも、同様にパスが見つからないよ!ってな問題が発生します。

例えば、Acme-MetaVar/test.pl を作って use してみます。

$ touch test.pl

test.pl の中身です。use しただけ。

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

use Acme::MetaVar;

実行するとエラーが出ます。prove-l なしで実行した時と同じですね。

$ perl test.pl
Can't locate Acme/MetaVar.pm in @INC (you may need to install the Acme::MetaVar module)
(中略)
 at test.pl line 5.
BEGIN failed--compilation aborted at test.pl line 5.

ですので、モジュール作成中のテストスクリプト中では、モジュールの位置を明示してあげる必要があります。

この test.pl からみてモジュール Acme::MetaVar があるのは同じ階層にある ./lib 以下です。ですので、このように1行追加します。

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

use lib ('./lib');

use Acme::MetaVar;

これでスクリプトを実行してもエラーが出なくなりました。

$ perl test.pl

テスト用のスクリプトを別の階層に書く場合は、相対パスにしろ絶対パスにしろ、モジュールがある場所を指定する必要があります。

テストを追加する

では、引き続きテストスクリプトを書いていきます。

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

use lib ('./lib');

use Acme::MetaVar;

my $am = Acme::MetaVar->new();
print $am->hoge() . "\n";    # hoge

prove -l で確認、無事動いてるようです。

さて、テスト用のファイルなのですが、Minilla が作成する雛形にはテスト用のスクリプト置き場があります。

t/ ディレクトリです。

ここにテストスクリプトをおくことで、 prove -l した際に一緒にテストしてくれるようになります。

せっかくなので、ここで先ほどの hoge メソッドを呼び出すテストを書いてみましょう。

先に作成したテスト用スクリプトは削除しておきます。

$ rm test.pl

今回は hoge メソッドを読んだときにちゃんと hoge という文字列が返ってきたら正常稼働とします。

$ touch t/01_hoge.t
$ vim t/01_hoge.t
use strict;
use Test::More 0.98;

use Acme::MetaVar;

my $am = Acme::MetaVar->new();

ok( $am->hoge eq 'hoge' );

done_testing;

文字列の比較演算子 eq を用いて、メソッドの返り値が hoge であるかを確認しています。

テストについてはここをよく参考にしたりしています。

gihyo.jp

perl-users.jp

さて、 prove -l で確認します。

$ prove -l
t/00_compile.t .. ok
t/01_hoge.t ..... ok
All tests successful.
Files=2, Tests=2,  0 wallclock secs ( 0.04 usr  0.01 sys +  0.22 cusr  0.06 csys =  0.33 CPU)
Result: PASS

おおっ!気持ちいい〜!

このように、テストしたい対象やテストするべきことがはっきりしているとテストも書きやすいです。

そう、意外に自分が何を求めているのか、わからないことも結構あるんですよね・・・人生と同じで・・・

ではここでまたgit add . & git commit しておきます

$ git add t/01_hoge.t
$ git commit -m "add test hoge"
[master 8c0ef38] add test hoge
 1 file changed, 10 insertions(+)
 create mode 100644 t/01_hoge.t

github.com

メソッドを増やす!その前に

ここで、新たにメソッド fuga を追加することになりました。使用イメージはこんな感じ。

print $am->fuga() . "\n";    # fuga;

Acme::MetaVar にサブルーチン追加していけば良さそうですね。

しかし、この先も同じような追加改修が続くかも知れません。piyo , foo, bar, buz ...

見通しの良いスクリプトにするために、メソッドごとにファイルを分けて管理することにします。

まずは、Acme::MetaVar から hoge メソッドを分離します。

Acme/MetaVar.pm から hoge サブルーチンを削除して、 Acme::Hoge モジュールを use します。

package Acme::MetaVar;
use 5.008001;
use strict;
use warnings;

our $VERSION = "0.01";

use Acme::Hoge; # 追加

sub new {
    my $class = shift;
    my $self = bless {}, $class;
    return $self;
}

1;
__END__
(中略)

そして、新規に Acme::Hoge モジュールを作ります。MetaVar.pm と同じ階層。treeで見るとこんな感じ。

├── lib
│   └── Acme
│       ├── Hoge.pm
│       └── MetaVar.pm

hoge メソッドを他のモジュール(今回は Acme::MetaVar )経由で利用できるよう、エクスポートしています。

package Acme::Hoge;
use 5.008001;
use strict;
use warnings;

our $VERSION = "0.01";

use Exporter 'import'; # エクスポーター

our @EXPORT = qw/hoge/; # エクスポートするメソッド

sub hoge {
    return "hoge";
}

では、これで prove -l でテストします・・・大丈夫ですね。

ではここでまた git で保存しておきます。

$ git add .
$ git commit -m "Moved the hoge method to another file"
[master ea84d86] Moved the hoge method to another file
 2 files changed, 16 insertions(+), 4 deletions(-)
 create mode 100644 lib/Acme/Hoge.pm

メソッドを分離しました。

github.com

fuga メソッドを「テストファースト」で追加してみる

さて、直近の課題たる fuga メソッドの実装です。・・・せっかくなので「テストファースト」で書いてみます。

先にテストを書くことで、スクリプトに何を求めているのか?をはっきりさせる効果があります。

では、hoge メソッドと同じように、fuga メソッドのテストを書いてみましょう。

と言っても、hoge をコピーしてファイル名と中身をちょっと変えるだけです。

fuga メソッドのテストファイルは 02_fuga.t とします。

$ cp t/01_hoge.t t/02_fuga.t
$ vim t/02_fuga.t

02_fuga.tの中身です。

use strict;
use Test::More 0.98;

use Acme::MetaVar;

my $am = Acme::MetaVar->new();

ok( $am->fuga eq 'fuga' );

done_testing;

では、prove -l です。

$ prove -l
t/00_compile.t .. ok
t/01_hoge.t ..... ok
t/02_fuga.t ..... Can't locate object method "fuga" via package "Acme::MetaVar" at t/02_fuga.t line 8.
t/02_fuga.t ..... Dubious, test returned 255 (wstat 65280, 0xff00)
No subtests run

Test Summary Report
-------------------
t/02_fuga.t   (Wstat: 65280 Tests: 0 Failed: 0)
  Non-zero exit status: 255
  Parse errors: No plan found in TAP output
Files=3, Tests=2,  1 wallclock secs ( 0.04 usr  0.02 sys +  0.32 cusr  0.08 csys =  0.46 CPU)
Result: FAIL

はい失敗しました。まだ Acme::Fuga も作ってないし、 Acme::MetaVar 内で use Acme::Fuga; してないからですね。

分かっていればテスト失敗も怖く無い・・・プッチ神父に通じる精神ですね。

では作っていきます。

package Acme::MetaVar;
use 5.008001;
use strict;
use warnings;

our $VERSION = "0.01";

use Acme::Hoge;
use Acme::Fuga;

sub new {
    my $class = shift;
    my $self = bless {}, $class;
    return $self;
}


1;
__END__
(中略)
package Acme::Fuga;
use 5.008001;
use strict;
use warnings;

our $VERSION = "0.01";

# Exporterを継承
use Exporter 'import';

# エクスポートする関数を記述
our @EXPORT = qw/fuga/;

sub fuga {
    return "fuga";
}

さて、どうでしょうか?

$ prove -l
t/00_compile.t .. ok
t/01_hoge.t ..... ok
t/02_fuga.t ..... ok
All tests successful.
Files=3, Tests=3,  0 wallclock secs ( 0.05 usr  0.02 sys +  0.32 cusr  0.08 csys =  0.47 CPU)
Result: PASS

はい、テストに通りました。

$ git commit -m "add fuga method"
[master 1218746] add fuga method
 3 files changed, 25 insertions(+)
 create mode 100644 lib/Acme/Fuga.pm
 create mode 100644 t/02_fuga.t

fuga メソッドを追加しました。

github.com

こんな感じで作っていくんだけど・・・

さも間違わずに迷うことなく出来上がったように書いてますが、もちろんそんなことないです。

たくさんのエラーに塗れておりますし、git status で確認しながらの作業です。

今回の記事は、だいたい2年くらい前の自分向けの記事でした。

追記

use Acme::Hoge;
use Acme::Fuga;

ってところ、単に hoge, fuga メソッドを使えるようにしているだけなのですが、これって継承では?やりたかったのって継承では?ってことで use base しました。

github.com

さらに追記

base はもう10年も前に非推奨ですね・・・ってことで use parent に置き換え〜

gfx.hatenadiary.org

github.com