Test-Simple-0.94 > Test::Tutorial

名前

Test::Tutorial - ごく基本的なテストを書くことについてのチュートリアル

概要

あーーーーー!!!!テストは嫌! 何をおいてもテストは嫌! ぶっても、むち打っても、デトロイトに送ってもいいけど、 テストを書かせないで!

*しくしく*

おまけに、そんな忌まわしいものの書き方など知りません。

あなたはこんな人ですか? テストを書くことは、ちょうど、ドキュメントを書き、指の爪を引き抜くことですか? テストを開き、読み、

    ######## いくつかの黒魔術を始めます。

テストはもうたくさんだと判断しますか?

いいでしょう。全ては行ってしまいました。あなたのために、黒魔術をすべて行いました ここにその仕掛けがあります…

テストの基本

最も基本的なテストのプログラム。

    #!/usr/bin/perl -w

    print "1..1\n";

    print 1 + 1 == 2 ? "ok 1\n" : "not ok 1\n";

1 + 1 は 2ですから、次のように表示します:

    1..1
    ok 1

このコードの意味: 1..1[1]「一つのテストを行います。」ok 1「最初のテストはパスしました」。 そして、これが、テストの魔法のほとんど全部です。基本的なユニットのテストはokです。 Test::Harnessは、テストの結果を解釈し、成功したか失敗したかを判断します(後では、もっと判断します)。

これらのコードを全部書くのは、すぐに飽きてしまいます。 ですが、幸運なことに、Test::Simpleがあります。これには、ok()という一つの関数があります。

    #!/usr/bin/perl -w

    use Test::Simple tests => 1;

    ok( 1 + 1 == 2 );

これは上のコードと同じことをします。 ok()は、Perlのテストの中心です。ここからは、自分で書く代わりに、ok()を使います。 ok() が真を返せば、テストは合格です。偽であれば、失敗です.

    #!/usr/bin/perl -w

    use Test::Simple tests => 2;
    ok( 1 + 1 == 2 );
    ok( 2 + 2 == 5 );

このコードから、

    1..2
    ok 1
    not ok 2
    #     Failed test (test.pl at line 5)
    # Looks like you failed 1 tests of 2.

1..2 「二つのテストを行います」 この番号は、テストプログラムが最初から最後まで実行されたことを保証し、 死んだり、いくつかのテストをスキップしたりしていないことも保証します。 ok 1「最初のテストはパスしました。」 not ok 2 「二番目のテストは失敗しました」。 Test::Simple はテストについての有用な特別のコメントを出力します。

怖くはありませんよ。こっちへきて、手を取って。モジュールをテストする例をみせましょう。 その例に、日付のライブラリである、Date::ICal をテストします。 Date::ICalは、CPANにありますので、コピーをダウンロードして、進んで下さい[2]。

どこからはじめましょう?

ここがテストの一番難しいところです.どこからはじめましょう? モジュールの全てをテストすることは、明らかに大きな仕事であり、圧倒されます。 最も良いのは、最初はからはじめることです. Date::ICal は、オブジェクト指向のモジュールです。ですから、オブジェクトを作ることからはじめればいい。 そう、new()をテストしましょう。

    #!/usr/bin/perl -w

    use Test::Simple tests => 2;

    use Date::ICal;

    my $ical = Date::ICal->new;         # オブジェクトを作成し
    ok( defined $ical );                # 何かを得たかをテストし
    ok( $ical->isa('Date::ICal') );     # そして、正しいクラスかテストします

これを実行すると、以下のようになります:

    1..2
    ok 1
    ok 2

おめでとう、最初の有益なテストが書けました。

名前

この出力は、全然記述的じゃないですね? 2つしかテストが無いなら、そのうちの一つが2番目のものだと分かりますが、 102もテストがあったら、どうでしょう?

それぞれのテストには、ちょっとした記述的な名前を二番目の引数として、ok()に与えることが出来ます。

    use Test::Simple tests => 2;

    ok( defined $ical,              'new() returned something' );
    ok( $ical->isa('Date::ICal'),   "  and it's the right class" );

今度は次のようになります...

    1..2
    ok 1 - new() returned something
    ok 2 -   and it's the right class

マニュアルのテスト

もっとも簡単にきちんとしたテストを作る方法は、ただマニュアルに書かれていることをテストします[3]。 "SYNOPSIS" in Date::ICalから何かを引いてきて、それを全てテストしましょう。ちょっとしたことです。

    #!/usr/bin/perl -w

    use Test::Simple tests => 8;

    use Date::ICal;

    $ical = Date::ICal->new( year => 1964, month => 10, day => 16, 
                             hour => 16, min => 12, sec => 47, 
                             tz => '0530' );

    ok( defined $ical,            'new() returned something' );
    ok( $ical->isa('Date::ICal'), "  and it's the right class" );
    ok( $ical->sec   == 47,       '  sec()'   );
    ok( $ical->min   == 12,       '  min()'   );    
    ok( $ical->hour  == 16,       '  hour()'  );
    ok( $ical->day   == 17,       '  day()'   );
    ok( $ical->month == 10,       '  month()' );
    ok( $ical->year  == 1964,     '  year()'  );

実行すると、次のようになります:

    1..8
    ok 1 - new() returned something
    ok 2 -   and it's the right class
    ok 3 -   sec()
    ok 4 -   min()
    ok 5 -   hour()
    not ok 6 -   day()
    #     Failed test (- at line 16)
    ok 7 -   month()
    ok 8 -   year()
    # Looks like you failed 1 tests of 8.

おぉー、失敗![4] Test::Simpleは、役に立つことに、何行目で失敗したのかを知らせてくれます。 けれど、他には何も知らせません。17を得なければならなかったのですが、そうはなりませんでした。何を得たのでしょうか?? わかりません。それを見つけるには、デバッガーでテストを再実行しなければならいか、print文に投げなければなりません。

その代わりに、Test::Simpleから、Test::Moreにきりかえましょう。 Test::More は、 Test::Simpleの行う全てのことを行えるし、もっと行えます! 実際、Test::Moreは、Test::Simpleが行う方法を正確に行います。 文字通り、Test::Simpleを取り外し、Test::Moreをそこに置くことが出来ます。 ちょうど、やろうとしていることです。

Test::More は、Test::Simpleより、働きます。 最も重要な違いは、Test::Moreはより有益に「ok」と言う点です。 一般的なok()でほとんどすべてのテストを書くことが出来ますが、ok()では、何が間違っているのかわかりません。 代わりに、is()関数を使いましょう。is()は、 あるものが他のものと同じであると思われると私たちに宣言させます:

    #!/usr/bin/perl -w

    use Test::More tests => 8;

    use Date::ICal;

    $ical = Date::ICal->new( year => 1964, month => 10, day => 16, 
                             hour => 16, min => 12, sec => 47, 
                             tz => '0530' );

    ok( defined $ical,            'new() returned something' );
    ok( $ical->isa('Date::ICal'), "  and it's the right class" );
    is( $ical->sec,     47,       '  sec()'   );
    is( $ical->min,     12,       '  min()'   );    
    is( $ical->hour,    16,       '  hour()'  );
    is( $ical->day,     17,       '  day()'   );
    is( $ical->month,   10,       '  month()' );
    is( $ical->year,    1964,     '  year()'  );

"Is $ical->sec 47?" "Is $ical->min 12?" 代わりに、is()を使うことで、 いくつか、より多くの情報を得ることができます。

    1..8
    ok 1 - new() returned something
    ok 2 -   and it's the right class
    ok 3 -   sec()
    ok 4 -   min()
    ok 5 -   hour()
    not ok 6 -   day()
    #     Failed test (- at line 16)
    #          got: '16'
    #     expected: '17'
    ok 7 -   month()
    ok 8 -   year()
    # Looks like you failed 1 tests of 8.

$ical->dayは、16を返しましたが、期待していたのは、17であったと知らせます。 素早いチェックにより、このコードがうまく動くか、テストを書き上げたときにミスをしたかを示します。 そこを変えるだけです:

    is( $ical->day,     16,       '  day()'   );

これで、全部動きます。

「これは、あれである」と言った種類のテストをするときはいつも、isを使うといいです。 isは、配列でも働きます。ただ、テストは、つねに、スカラーコンテキストで動くので、 次の方法で、リストの中の要素がいくつあるかをテストできます[5]。

    is( @foo, 5, 'foo has 5 elements' );

テストは間違っている時もある

間違いは、とても大切なレッスンになります。コードにはバグがある。テストはコードである。 ゆえに、テストにはバグがある。失敗したテストは、コードにバグがあることを意味します。 しかし、テストが間違っている可能性を排除してはなりません。

反面、時期尚早に、ただ、バグを見つけるのに苦労しているからといって、 テストが不正確であると断言しようという誘惑にかられないように。 テストを無効にすることは、敏速にとれるものではありませんし、仕事から逃れて、避けるために、 テストを無効にしてはいけません。

たくさんの値のテスト

多くの違ったエッジケースでコードをテストしようとするとき、多くの値をテストしたいと思います。 1970年以前では、動くだろうか?2038年の後は?1904年以前は?10,000年後に、トラブルが起きるか? 閏年を正しく得られるか? 上のコードを繰り返し続けるか、ループを試みるかを、期待することが出来ます。

    use Test::More tests => 32;
    use Date::ICal;

    my %ICal_Dates = (
            # An ICal string     And the year, month, day
            #                    hour, minute and second we expect.
            '19971024T120000' =>    # from the docs.
                                [ 1997, 10, 24, 12,  0,  0 ],
            '20390123T232832' =>    # after the Unix epoch
                                [ 2039,  1, 23, 23, 28, 32 ],
            '19671225T000000' =>    # before the Unix epoch
                                [ 1967, 12, 25,  0,  0,  0 ],
            '18990505T232323' =>    # before the MacOS epoch
                                [ 1899,  5,  5, 23, 23, 23 ],
    );


    while( my($ical_str, $expect) = each %ICal_Dates ) {
        my $ical = Date::ICal->new( ical => $ical_str );

        ok( defined $ical,            "new(ical => '$ical_str')" );
        ok( $ical->isa('Date::ICal'), "  and it's the right class" );

        is( $ical->year,    $expect->[0],     '  year()'  );
        is( $ical->month,   $expect->[1],     '  month()' );
        is( $ical->day,     $expect->[2],     '  day()'   );
        is( $ical->hour,    $expect->[3],     '  hour()'  );
        is( $ical->min,     $expect->[4],     '  min()'   );    
        is( $ical->sec,     $expect->[5],     '  sec()'   );
    }

これで、ただ、%ICal_Datesに日付の束を加えるだけで、テストできます。 さて、たくさんの日付をテストするのが少ない仕事になりましたが、 考えるように、ただ、日付を投げるだけにしたいかもしれません。 唯一の問題は、毎回、use Test::More tests => ## の行を、調節しなければならない事です。 このことは、急速に煩雑になるでしょう。

2つの方法で、もっとうまくやれます。

一つめの方法は、、plan()関数を動的に使って、計画を計算します。

    use Test::More;
    use Date::ICal;

    my %ICal_Dates = (
        ...same as before...
    );

    # For each key in the hash we're running 8 tests.
    plan tests => keys(%ICal_Dates) * 8;

    ...and then your tests...

もしくは、より、フレキシブルに、no_plan を使います。これにより、 いくつかのテストをただ走らせます。いくつなのかは知りません[6]。

    use Test::More 'no_plan';   # instead of tests => 32

これで、ただ単にテストを加えるだけで、全てのテストがいくつかあるか数えなくても、いくつでも、テストできます。

有益な名前

次の行をみてください

    ok( defined $ical,            "new(ical => '$ical_str')" );

テストしていることと、テストしている ICal ストリング自身の詳細を、名前に加えます。 次のような結果が出ます:

    ok 25 - new(ical => '19971024T120000')
    ok 26 -   and it's the right class
    ok 27 -   year()
    ok 28 -   month()
    ok 29 -   day()
    ok 30 -   hour()
    ok 31 -   min()
    ok 32 -   sec()

失敗の中に何かあるなら、それが一つであり、それによって、問題を追跡するのが簡単になることがわかるでしょう。 ですから、ちょっとしたデバッグ情報をテストの名前に入れてみて下さい。

そのテストが何をテストするかを記述しておけば、 失敗したテストをデバッグする際、自分や他の人がテストを実行するのが、簡単になります。

テストをスキップする

Date::ICal で用意されているテストを探し回って、t/01sanity.tに、次のものを見つけました[7]。

    #!/usr/bin/perl -w

    use Test::More tests => 7;
    use Date::ICal;

    # Make sure epoch time is being handled sanely.
    my $t1 = Date::ICal->new( epoch => 0 );
    is( $t1->epoch, 0,          "Epoch time of 0" );

    # XXX This will only work on unix systems.
    is( $t1->ical, '19700101Z', "  epoch to ical" );

    is( $t1->year,  1970,       "  year()"  );
    is( $t1->month, 1,          "  month()" );
    is( $t1->day,   1,          "  day()"   );

    # like the tests above, but starting with ical instead of epoch
    my $t2 = Date::ICal->new( ical => '19700101Z' );
    is( $t2->ical, '19700101Z', "Start of epoch in ICal notation" );

    is( $t2->epoch, 0,          "  and back to ICal" );

たいていの非UNIX OS では、エポックの始まりが異なっています[8]。 ほとんどの部分でPerlは、差異をならしていますが、ある部分では、違ったように行います。 今、正確には思い出せませんが、MacPerlはその一つだと思います[9]。 なので、ただテストにコメントをおくだけではなく、 はっきりと、決して働かないように言い、テストをスキップすることが出来ます。

    use Test::More tests => 7;
    use Date::ICal;

    # Make sure epoch time is being handled sanely.
    my $t1 = Date::ICal->new( epoch => 0 );
    is( $t1->epoch, 0,          "Epoch time of 0" );

    SKIP: {
        skip('epoch to ICal not working on MacOS', 6) 
            if $^O eq 'MacOS';

        is( $t1->ical, '19700101Z', "  epoch to ical" );

        is( $t1->year,  1970,       "  year()"  );
        is( $t1->month, 1,          "  month()" );
        is( $t1->day,   1,          "  day()"   );

        # like the tests above, but starting with ical instead of epoch
        my $t2 = Date::ICal->new( ical => '19700101Z' );
        is( $t2->ical, '19700101Z', "Start of epoch in ICal notation" );

        is( $t2->epoch, 0,          "  and back to ICal" );
    }

ここで、ちょっとした魔法が起きます。 MacOS以外で動いた場合,全てのテストは普通にテストされます。 しかし、MacOS上の場合、skip()によって、SKIPブロックの中の全ての内容が飛ばされます。 決して動くことはありません。 かわりに、テストがスキップされていることをTest::Harness に伝える特別な出力がプリントされます。

    1..7
    ok 1 - Epoch time of 0
    ok 2 # skip epoch to ICal not working on MacOS
    ok 3 # skip epoch to ICal not working on MacOS
    ok 4 # skip epoch to ICal not working on MacOS
    ok 5 # skip epoch to ICal not working on MacOS
    ok 6 # skip epoch to ICal not working on MacOS
    ok 7 # skip epoch to ICal not working on MacOS

これは、MacOS上で,テストは失敗しないという意味です。 これで、MacPerlのユーザーからの、動かないと分かっているテストが動かないと報告するemailが少なくなります。 テストのスキップについて慎重になるように。スキップのテストは、動かない、決して動かないテストに対してあるものです。 本物のバグをスキップする為のものではありません(すぐにそうなるでしょう)。

このテストは、まったく、完全にスキップされます[10]。以下のコードは、動きます。

    SKIP: {
        skip("I don't wanna die!");

        die, die, die, die, die;
    }

ToDo テスト

Date::ICalのmanページをざっと目を通していて、次のものに遭遇しました:

   ical

       $ical_string = $ical->ical;

   オブジェクトの日付の取得、または、セット。
   すべての妥当な ICal date/time 文字列を使って、オブジェクトに日付をセットする。

「取得またはセット」。ふむ、Date::ICalのテストで、日付をセットするical() を使うテストを見ませんでした。 私は、次のように書くでしょう。

    use Test::More tests => 1;
    use Date::ICal;

    my $ical = Date::ICal->new;
    $ical->ical('20201231Z');
    is( $ical->ical, '20201231Z',   'Setting via ical()' );

これを実行すると、

    1..1
    not ok 1 - Setting via ical()
    #     Failed test (- at line 6)
    #          got: '20010814T233649Z'
    #     expected: '20201231Z'
    # Looks like you failed 1 tests of 1.

おぉー! 実行されないようだ。さて、これを修正する時間がないと想定しましょう。[11] ふつうは、このテストをコメントにして、ToDoリストに書き留めておくでしょう。 その代わりに、TODOブロック内でラッピングして(包んで)、「このテストが失敗した」という明らかな状態にしましょう。

    use Test::More tests => 1;

    TODO: {
        local $TODO = 'ical($ical) not yet implemented';

        my $ical = Date::ICal->new;
        $ical->ical('20201231Z');

        is( $ical->ical, '20201231Z',   'Setting via ical()' );
    }

実行すると、ちょっとした違いがあります:

    1..1
    not ok 1 - Setting via ical() # TODO ical($ical) not yet implemented
    #          got: '20010822T201551Z'
    #     expected: '20201231Z'

Test::Moreは、「一つのテストの中の一つが失敗したようだ」とは言いません。 この「# TODO」は、Test::Harness に「これは、失敗すると思われる」と伝え、 Test::Harnessは、失敗したテストを成功したテストのように扱います。これで、 バグを修正する前にテストを書くことができます。

もし、TODOテストがパスすると、Test::Harness は、テストが、「思いがけず成功した」と報告します。 これが起きれば、local $TODOでTODOブロックを外して、本当のテストに変えれば良いでしょう。

汚染モードでのテスト

汚染モードは楽しいものです。全ての全体的な特徴のなかでも、もっとも全体的なものです。 汚染モードを付けると、汚染モードは、プログラムの全てのコードと全てのモジュール(と、それらが使っている全てのモジュール)に影響します。 コードの一つでも、汚染されていれば、全てが爆発します。 このことを念頭に置いて、汚染モードの下で、モジュールが動くのを保証することは、とても重要です。

テストを、汚染モードで走らせるのは、とても簡単です。 #!行に、-Tを投げるだけです。Test::Harness は、#! 行のスイッチを読み、テストでそのスイッチを使います。

    #!/usr/bin/perl -Tw

    ...test normally here...

で、make testをすると、テストは汚染モードと警告を有効にして走るでしょう。

脚注

  1. 最初の数字は実際何も意味していませんが、1でなければいけません。重要なのは、二番目の数字です。

  2. 以下に進むため、バージョン1.31を使います。このバージョンには、バグが少々ありますが、 大丈夫です -- テストでバグを明らかにしましょう。

  3. 実際に、このステップをもっと取り入れ、マニュアル自身をテストできます。 Test::Inlineを見なさい。 (以前の Pod::Tests)

  4. このテストには、間違いがあります。 何! 私が、仕組んだって?

  5. 後で、リストの内容をテストします。

  6. しかし、テストプログラムが途中で死んだら、何が起きるのだろうか?! どれくらいのテストを動かしているのか書かないで、どうやって失敗したのかわかるだろうか? 問題ありません。Test::Moreは、いくつかの魔法を使い、その死を捕らえ、 テストを失敗に変えます。たとえ、全てのテストが、その場所で合格したとしても。

  7. ちょっとだけ綺麗にしました。

  8. たいていのOSは、ある日付からの秒数として時間を記録します。 この日付が、エポックの始まりです。UNIXのエポックの始まりは、標準時の1970年の1月1日の深夜です。

  9. MacOSのエポックは、1904年の1月1日の深夜です。VMSのエポックは、1858年の11月17日の深夜です。 ですが、vmsperlは、UNIXのエポックをエミュレートしているので、問題はありません。

  10. SKIPブロックの中にコードが在る限り、少なくともコンパイルされます。どうやってるかは聞かないで下さい。 フィルターではありません。

  11. 簡単なバグを直すのを避けるための方法として、TODOテストを使おうとしてはいけません!

原文まま

Michael G Schwern <schwern@pobox.com> and the perl-qa dancers!

原文まま

Copyright 2001 by Michael G Schwern <schwern@pobox.com>.

This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.

Irrespective of its distribution, all code examples in these files are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.