Skip to content.

Sections
Personal tools
You are here: Home » コミュニティ » masarl memorial » masarl.cocolog-nifty.com » main » 2004 » 07 » Cotton Bolls: PerlUnitと動的スコープ

Cotton Bolls: PerlUnitと動的スコープ

Document Actions

« ポケットCVSリポジトリ | トップページ | データベース定義XML »

2004.07.11

PerlUnitと動的スコープ

今回の仕事はPerlを使ったWebアプリの開発…って,なんとPerlだ.正直この歳(36)でPerlなんて夢にも思わなかった.

そもそも,最近はプロジェクトが変わるごとに開発言語も変わっている.curl,Java,C#と来て今度はPerlか….去年の11月から開発言語が4つも変わっている(泣).言語が変わる度に文法の勉強,開発環境の整備,パッケージの調査….いくらなんでもしんどいです.こんなんじゃプロジェクト工数かかるのも無理ないわな.

でも,この歳でまだプログラミング開発の仕事をやらせてもらってるのはありがたいと思う.昔から上流の仕事も管理職もまったく興味なし.そういう意味では今の会社に感謝している(…と一応持ち上げておく).

ところで,Perlは良くも悪くもバッドノウハウのかたまり言語だと思う.少し間違えただけですぐハマる.use strictや-wでもまだ足りない.他の言語では明らかにエラーになるところを余裕で素通りする.例えば,Error.pmのtry...catch.最後のセミコロンを忘れたら大変だ.動作がおかしくなるだけで文法エラーも何もでない:

try {
    $package->assert_str_equals($expected, $actual);
}
catch Test::Unit::Exception with {
    my $e = shift;
    $e->throw_new(-package => $package,
                  -file    => $filename,
                  -line    => $line,
                  -text    => $e->{-text});
}; # <= このセミコロンが重要!

他にハマッたのは親クラス宣言.親クラスは

use base qw(SuperClass);

と書くのが正解だが,編集中にうっかりして

use base SuperClass;

とやってしまった.こうするとUnknown Errorというエラーメッセージが出るだけだ.エラー箇所やヒントは一切ない(cygwin perl).一体なんなんだろう,この言語は….

その一方で,Perlはプリミティブであるがゆえに工夫次第でかなりのことができてしまう.これでPerlにのめりこむ人も多いのではないかな.

例えば,先ほどのtry...catch.Perlには例外処理なんてないのに,Error.pmというモジュールで実現できる.Exporterを使えばMixinも可能.なんとAspect.pmなんてモジュールもあるらしい(参考:Object Oriented Programming in Perl).

こういう機能は作る分にはすごく面白いと思う.けれども,利用する側はかなり不便だ.try..catchすら標準モジュールにないため,あるモジュールを利用するときは依存している他のモジュールを何個何個もインストールするはめになる.とにかく,いろんなモジュールが絡むため,再配布のことを考えるとかなり面倒.もし依存しているモジュールのバージョンが変わっていたら悲惨だろう.この辺Perlユーザはどう考えているのかな.

というわけで,今回もPerlのテスト環境や開発環境を整えることから始めることになった.特にこういう言語では,ユニットテストがしっかり書けないと泥沼になるのは目に見えているからだ.

まずはMutliTestRunner.pl.これは,複数のTestCaseを個別に実行したり,一度に実行したりできるようにするためのPerlスクリプトだ.これは,昔Javaで作ったMultiTestRunner(JUnit 実践講座 - GNU Make を使った開発環境の構築)のPerl版だ.時間がなかったのでテストメソッド単位の実行は省略した:

#!/usr/bin/perl -w

use strict;

use Test::Unit::TestSuite;
use Test::Unit::Debug qw(debug_pkgs);
use Test::Unit::TestRunner;

my $suite = Test::Unit::TestSuite->empty_new("A Test Suite");

foreach my $file (@ARGV) {
    my $package = Test::Unit::Loader::compile($file);
    my $test_case = Test::Unit::Loader::load_test_case($package);
    $suite->add_test($test_case) if $test_case;
}
my $testrunner = Test::Unit::TestRunner->new();
$testrunner->do_run($suite);

次にPerlUnit Converter.移植するのはかなり苦労したが,おおむね次のような形にまとめることができた:

package Test::Unit::ConvTest;
use strict;

use base qw(Test::Unit::TestCase);
use Test::Unit::Conv;

{
    package Test::Unit::ConvTest::TestBean;
    sub new {
        my $class = shift;
        my $self = {
            field1 => 'field1_val',
            field2 => 'field2_val',
        };
        return bless $self, $class;
    }
    sub get_field3 {
        return 'field3_val';
    }
}

sub TestBean {
    return __PACKAGE__ . "::TestBean";
}

sub test_bean_value_converter {
    my $self = shift;

    my $converter = DefaultConverter->new;
    $converter 
        = BeanValueConverter->new($converter, TestBean, "field1", "field2", "field3");
    my $expected = [
        "field1_val",
        "field2_val",
        "field3_val",
    ];
    $converter->assert_equals($expected, TestBean->new);
}

こんな感じでPerlの勉強をしながらテストケースを書いている.

さて,Perlには他の言語にはあまり見られない動的スコープ(Dynamic Scope)というものがある.これを使うとHello Worldのテストコードを簡単に書くことができる.すなわち,標準出力(STDOUT)を動的スコープでフックするわけだ:

{
    local *STDOUT;
    open(STDOUT, ">temp.out") || die "Can't redirect stdout";
    print "Hello World\n";
}

こうするとHello Worldの文字列はtemp.outファイルにリダイレクトされることになる.このことを利用して作ったのがStdoutCaptureクラスだ:

package Test::Unit::IO::StdoutCapture;
use strict;
use File::Temp qw(tempfile);
sub do(&) {
    my $block = shift;
    my ($f, $filename) = &tempfile();
    {
        local *STDOUT;
        open(STDOUT, ">$filename") || die "Can't redirect stdout";
        $block->();
    }
    open(TEMPFILE, "<$filename");
    my @out;
    while(<TEMPFILE>) {
        push @out, $_;
    }
    close(TEMPFILE);
    unlink($filename);
    return @out;
}
1;

使用例は以下の通り:

package Test::Unit::IO::StdoutCaptureTest;
use strict;

use base qw(Test::Unit::TestCase);

use Test::Unit::Conv;
use Test::Unit::IO::StdoutCapture;

sub test_basic {
    my $self = shift;
    my @out = Test::Unit::IO::StdoutCapture::do {
        print "line1\n";
        print STDOUT "line2\n";
        print "line3";
    };
    my $converter = DefaultConverter->new;
    my $expected = [ "line1\n", "line2\n", "line3" ];
    $converter->assert_equals($expected, \@out);
}
1;

このように,動的スコープでテストコードが楽に書けることがあるようだ.

ところで,Lispにも動的スコープがある.今回は,Emacs Lispでperldocコマンドを作っているときに役立った.

もともとperldocはPerl標準添付のシェルコマンドで,モジュールやサブルーチンの使い方を出力する.unixのmanコマンドのようなものだ.なかなかperlの関数が覚えられず,シェルで何度もperldocを動かしていたのでEmacsから呼べるperldocコマンドを作ることにした.

perldocコマンドはEmacsのmanコマンドみたいなもので十分だ.そこで,man.elのソースを見てみる.すると,manコマンドの本体はMan-getpage-in-background関数で,manual-program変数にシェルから起動するmanコマンド名を入れていることがすぐわかった.そこで,このmanual-program変数を動的スコープでフックする:

(defun perldoc (word)
  (interactive "sperldoc: ")
  (let ((manual-program))
    (setq manual-program "perldoc")
    (Man-getpage-in-background word)))

なんと,たったこれだけでperldocコマンドが動くようになった.普通ならもっとlispのコードを書かないといけないのにこれだけで済むとは驚いた.このように,動的スコープはグローバル変数みたいなものだからあまりお勧めできないが,ちょっとしたものなら役に立つ.PerlUnitでのうまい応用例も見つかればいいと思っている.

08:12 PM in Perl | 固定リンク

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/946002

この記事へのトラックバック一覧です: PerlUnitと動的スコープ:

コメント

コメントを書く