D-7 <altijd in beweging>

Day to day life of a Perl/Go/C/C++/whatever hacker. May include anything from tech, food, and family.

タグ:test

スクリプトからサブルーチンだけをインポートする
Script::Sub::Importというモジュールを作成しました
http://d.hatena.ne.jp/perlcodesample/20091130/1258979624

これは大変いただけない。perlcodesampleというIDがさらに初心者に信用させがちだからさらにいただけない。なので苦言を呈させてもらう。

命題はスクリプト内で使っている関数のテストをしたい、なんだよね。
なら、いくつかこんなひどいハックじゃないやり方がある。

まず第一の、そして一番まともな方法としては「モジュール化」を考えるべき。元のスクリプトがこんな感じだとする:
#!/usr/bin/perl use strict; main(); sub main { # pseudocode... foo(@ARGV); bar(); } sub foo { ... } sub bar { ... }
だったら、こんなlib/MyApp.pmを作って、そこにこんなコードを書いておけばいい:
package MyApp; use strict; sub foo { ... } sub bar { ... }
で、以下のようにスクリプト内で呼び出せばいい。
#!/usr/bin/perl use strict; use MyApp; main(); sub main { MyApp::foo(@ARGV); MyApp::bar(); }
これならテストはそれこそ普通のモジュールのテストを書くときのようにすればいい。
でもモジュール化したくない時というのもわかる。例えばlibディレクトリを使わずにスクリプト単体で完結させたい、なんて状況も考えられる。なのでそれは理解できるのだが、それなら以下のように caller()を確認すればメイン関数は走らないので、スクリプトの実行はせずに関数群をrequireできるようになる。
#!/usr/bin/perl use strict; main() unless caller(); # perl script.plで呼び出された時は空配列が返るのでmain()実行 # テストスクリプトやその他の場所から呼び出された場合は # なんらかの値が返るので、実行されない sub main { ... } sub foo { ... } sub bar { ... }
これなら、普通にテストスクリプトでrequireなりなんなりして、スクリプト内の関数をぼこっと使える:
# テストファイル require 'script.pl'; ok( foo( ... ) ); is( bar(...) );
caller()の手法の代わりに$ENV{TEST}みたいなフラグを使うのもありだろう。

追記:そういえば書こうとして忘れてた。

@tokuhirom: .pl にはほとんど何も書かないのが最近のトレンドなんだけどな。

そうそう。ということで、こういうのでもいいわけです:
package App::MyApp; use Moose; # or Mouse, or Class::Accessor::Fast, whatever sub run { my $self = shift; $self->foo(@args); $self->bar(); } sub foo { ... } sub bar { ... } # in script.pl use strict; use App::MyApp; App::MyApp->new()->run(@ARGV); # MooseX::Getoptならもっと簡単
追記終了。

いずれにせよ、いきなりCPANに登録する前に#shibya.pmで聞くとか、ブログでまずつぶやくとかしてみて、一旦回りの評価を聞いてからやってもよかったのではないか。

初心者に道を示すなら特にその辺りには気を遣って欲しいと思う。個人的にはCPANからも削除したほうがいいと思うな。
    このエントリーをはてなブックマークに追加 mixiチェック

[2010/10/21 追記] Makefileいじらないでもいいようなモジュールが出てますのでこちらの新しい記事もどうぞ



Test::mysqldを使うとクールにMySQLを起動させられるので、それを使おうとしたんだ。でもおいらのローカルにあるmysqlをMacPortsのmysqlで、ファイルレイアウトがメタメタなんだ。だからまずこんな感じで、Test::mysqldを継承するMyApp::Test::mysqldを書いたわけさ!

必要とあればMacPortsとかの環境じゃなくてもMYSQL_INSTALL_DBとMYSQLDを設定すればテスト時にTest::mysqldが見るバイナリを変更できるのがミソだね!

さて、これを使っても、何個もテストスクリプトがある時に一回一回mysqlを立ち上げ直してちゃ意味がない。遅いし、毎回DBの設定をしなくちゃいけないじゃないか!

だもんで、まずmake testが走るときに前もってTest::mysqldを起動させ、make test 終了とともにDBが停止するような仕組みをMakefile.PLに書いてみたわけさ!


Oh, how very unpleasantly hack-ish!

汚いけど、これでMakefileをいじっちゃうわけだね!ちなみにModule::Installも同じような力業でMakefileを編集してるよ!

ともあれこれでmake testとした時に自動的にMyApp::Test::mysqldが立ち上がるってわけさ。$SIG{INT} = \&exitってしておくのは、ユーザーがCtrl-Cした時にTest::mysqld::DESTROY()が走るようにするためのおまじないだよ!

mysqldが立ち上がったら、そこへの接続情報を$ENV{TEST_DSN}につっこんでおくわけさ。これでテスト側からこの値を参照するだけでDBに接続できるわけだね。

ここまで来たらもう一歩!

  • DBIx::Classを使ってるんだから、DBスキーマも自動的にdeployしたいじゃない!
  • それぞれのテストでTEST_DSN呼んだりするの面倒くさいよね!
  • 単体のファイルを実行した時は、mysqld起動+deployを各ファイルでやってほしいけど、make testの場合は1回だけ実行したい!

こんなワガママなキミはそれ用のラッパーを作ると便利かもね!そこでこんなMyApp::Testを書いたわけさ(3回目)!

ここでは僕の自家製DIコンテナOrochiってのを使ってるのでわからないことだらけかもしれないけど、重要なのは以下のことさ!

  • MyApp::Schemaは$self->assembler->get('schema/master')とすることにより、すでに生成されているならその値を、そうでなければあらたに生成した値を得ることができる。
  • MyApp::Schemaの生成には 'schema/master/connect_info'というデータが必要
  • connect_infoのDSN部分は、$ENV{TEST_DSN}が設定されているなら、それを利用する(mysqldはMakefileによって起動している)が、そうでないなら、自分でmysqldを立ち上げる。
  • その際、空のデータベースを立ち上げただけでは意味がないので、deployフラグもつけておく
  • deployフラグがたっている場合は、DIコンテナからschemaを取り出す際にdeployもしてしまう
こんなことをしておけば、make test時に一回一番最初だけに実行されるt/000_setup.tみたいなファイルを用意してMyApp::Test->new()->deploy()すればmake test中には一回しか起動+deployはされないし、各テスト実行時にもMyApp::Test->new()しておけばMyApp::Schemaを使用する時に自動的に同じ事が起こるわけさ!

じゃあもう眠いから、アディオス!

    このエントリーをはてなブックマークに追加 mixiチェック

これから新幹線の中で書きます。GunghoPixisで色々Moose::RoleとTest::FITesqueでやってみた結果についてぼちぼち。ちなみにTest::FITesqueはJay Shirleyさんに言われて、えーと思ってたんだけどid:lopnorががっちり基本を書いてくれて、それでようやく理解した。lopnor++
    このエントリーをはてなブックマークに追加 mixiチェック

このページのトップヘ