tokuhirom's blog.

'; DROP DATABASE database();

Dumper the lexical variables after failed test case(Just Idea)

Hi,

I got an another hacking idea for perl testing.

Hook the Test::Builder::ok using PadWalker. Dump the lexical vars in testing context.

package Test::Power {
    no warnings 'redefine';
    use Test::Builder ();
    use PadWalker ();
    use Data::Dump::Streamer;
    use Data::Dumper;
    my $orig_ok = \&Test::Builder::ok;
    *Test::Builder::ok = sub {
        # my ($self, $test, $msg) = @_;
        {
            local $Test::Builder::Level = $Test::Builder::Level + 1;
            $orig_ok->(@_);
        }
        unless ($_[1]) {
            my ($pkg, $filename, $lineno) = caller($Test::Builder::Level);
            if (defined $pkg) {
                my $lexicals = PadWalker::peek_my($Test::Builder::Level+1);
                local $Data::Dumper::Terse = 1;
                local $Data::Dumper::Indent = 1;
                local $Data::Dumper::Maxdepth = 3;
                $_[0]->diag(Dumper($lexicals));
            } else {
                # users testing env is broken.
            }
        }
    };
}

Example testee code.

package Square {
    use Moose;
    has x => (is => 'rw');
    has y => (is => 'rw');
    sub area {
        my ($self) = @_;
        return $self->x * $self->y;
    }
}

Example test code

package main {
    use Test::More;

    my $sq = Square->new(x => 4, y => 5);
    is($sq->area, 24);

    done_testing;
}

Then, you get a dumped lexical vars automatically. It may be useful for debugging(I hope).

not ok 1
#   Failed test at hoge.pl line 51.
# {
#   '$sq' => \bless( {
#       'y' => 5,
#       'x' => 4
#     }, 'Square' )
# }
#          got: '20'
#     expected: '24'
1..1
# Looks like you failed 1 test of 1.

It's just idea. What do you think?

Thanks.

Testing Web Application 2011秋

最近の僕のテスティングな日々についてまとめておきますよ。

【前提】

  • Perl でかかれたウェブアプリケーション
  • 自社開発のウェブアプリケーション
  • 一日に何度も deploy されるレベル
  • フルテストを全員がうごかしているとはかぎらない

テストの目的

俺がつくった部分を他の誰かがぶっこわさないかどうかを監視する

テストかいてなくてエッジケースで他の誰かがちょっと変更してぶっこわれたりするとダルいですよね。ここでいう他の誰かというのは未来の自分も含みます。

なんども手でうごかすのがダルい部分の処理を自動でテストしたい

たとえば API まわりとかそういうのです。ちょっといじってはブラウザでポチポチやるとか、だるい! ってなります。

とにかく楽に、お気軽に。

お気軽にテストを追加できて、お気軽にテストを実行できる。というのがなによりも重要です。

追加しようとおもったときに「どう追加したらいいかわからない」という状況になりがちなので、あらかじめいろいろな構成要素ごとにテストをひととおりかいてみるというのも重要だとおもいます。O/R Mapper のクラスのテストとか、model のテストとか、コントローラのテストとか、JSON API のテストとか。。

Ukigumo をつかった CI

Ukigumo を開発環境でうごかしています。これで、誰かがテストをぶっこわしても安心です。だいたいチームの人数がふえてくると、テストをうごかさない人もでてきますし、自分でも毎回すべてのテストをうごかしているわけではありません(時間かかるから)。

ちなみに現在のテストのフル実行にかかる時間は36秒です。

ExtUtils::MakeMaker でテストをうごかさない

ExtUtils::MakeMaker を素でつかうと blib/ とかつかってきてうざいので、Makefile.PL に以下のような hack をほどこしてあります。ウェブアプリケーションで blib/ とかにコピーする必要ないですよね。実運用でも blib/ でうごかさないし、インストールもしませんし。

# 素の make test つかうと blib/ にコピーされたりしてうざいので prove 直接たたいてやります
sub MY::test_via_harness {
        "\tprove -r t"
}
# pm_to_blib とか余計なファイルつくらせないようにするためのおまじない
sub MY::top_targets {
        <<"..."
all ::
\t
pure_all ::
\t
subdirs ::
\t
config ::
\t
...
}

DB は開発環境のものをピーコしてつかう

mysqladmin -uroot create myapp
mysqldump -uroot -d myapp-dev | mysql -uroot myapp

するかんじがいいですね。Test::mysqld などをつかってもいいんですが、いかんせんテストの起動がおもくなってしまいます。

make test の間で一回しかうごかさない、みたいな hack もあるとおもいますけども、開発環境ではすでに mysqld たちあがってるから別個にたてる必要性が皆無ですよね、ってかんじです。

prove をつかいこなす

.proverc に以下のようなかんじでかいてます。 App::Prove::Plugin::SchemaUpdater っていう prove のプラグインをある人がかいていて、それがインテリジェントに開発環境の schema 構成を、テスト用のデータベースにピーコします。本当は schema をちゃんと管理したほうがいいんですが、schema ファイルをアップデートせずにいきなり ALTER うちこむ人などが多発しがちなので、こういうかんじでやっつけています。

"--exec=perl -Ilib -I. -Mt::Util"
--color
-Pt::lib::App::Prove::Plugin::SchemaUpdater

t::Util を充実させる!

テストを書くときって、まずどうやってかいたらいいのかわからないっていうのがあるとおもうんです。だから、いろいろがんばるというのが肝要です。

たとえば、テスト用の user データをつくる、みたいなのって頻出パターンだから、

my $user = create_user(name => 'John');

みたいなかんじで、ちゃちゃっとつくれるようにしておくというのが重要です。いまやってるやつだとこういう感じのメソッドが8つぐらいあります。

mock_http() みたいな関数をつくる!

LWP::UserAgent をモッキングする LWP::Protocol::PSGI っていうのがあるんですけど、これつかうと全部 PSGI のハンドラで処理しなくちゃいけなくて、元の LWP へアクセスをながすみたいなのができなくて不便なので、手で hack したクラスををつくってます。

mock_http(); ってやると、サービスの URL だけをフックして、*.psgi をコールします。これで、普通に LWP::UserAgent や Test::WWW::Mechanize などをつかって気軽に HTTP based testing が可能となります。

まとめ

お気軽にテストを追加して、安心して開発ができる環境をつくることについてかんがえてみました。

LL における継続的テストの重要性

最近、お仕事で継続的にテストを行ってくれるためのサーバを用意しました。

これまでも一応、automated test 的なものはあったんですが、

  • 担当者がかわるとテストがメンテされなくなる
  • テストの実行順序によってコケるテストがある
  • なぜかテストを書いた人の環境じゃないとテストが通らない
  • テストを開発者がちゃんと実行しない
    • DB や httpd をとおすテストはどうしても時間がかかってしまうため億劫になりがち

等の問題が発生していました。

まあ、テストなしでちゃんと運用できてるんなら問題はないんですが、やっぱりソフトウェアにバグはつきものなので、それをすこしでも減らすために継続的にテストを動かすサーバをつくろうと決心しました。

基本的に上記のような現象は、開発している各メンバーが気をつけていれば回避できる問題ではあるのですが、複数人のチームである以上、全員が*気をつける*というのは無理があると個人的には考えています。複数人が意識的になにかをするぐらいなら*仕組み*なり*それ用の人を雇う*なりしてやるのが本筋だと自分は考えています。

実装方法

といっても、そんなにむずかしいことはしてなくてシェルスクリプトを毎時動かしているだけです。

最初はPlagger でつかって*いた* Test::Chimps というのを使ってやろうかと思っていたんですが、設定の方法がややこしい上に Jifty::DBI とかに依存していて、依存モジュールがやたらに多い(そして普段自分がつかってないモジュールが多い)上に、設定が意外とめんどかったので挫折して、シェルスクリプトにしました。まあ、実際のところそんなに web interface はいらないので(笑)

cron で定時に実行して、crontab の MAILTO にそれ用の ML のアドレスを設定するだけ。Test::Harness がコケたら die するから、それでわかります。

まとめ

まだ先週からはじめたばっかりなんで効果のほどはまだわかりませんが、とりあえずテストのメンテはすすみそうです。

おまけ(Perl::Critic のはなし)

また、この方法をとることのメリットとして 98_perlcritic.t を定時に動かせるということがあります。Perl::Critic を使うと最低限のラインの品質は保証されるので、Perl::Critic をまだ導入されてない Perl user の方は是非導入しましょう。

おまけ(TestTools のはなし)

今回、継続的なテストをやるために Sledge なウェブアプリケーションを自動化するためのフレームワーク的なものを開発したので、そのうち公開しようと思っています。