10

これは、スタック オーバーフローに対する私の最初の質問です。いくつかのルールに違反している場合は、事前にお詫び申し上げます。

Perl モジュールのテストと Test::More の機能の使用について説明している Intermediate Perl, 2nd ed. の第 14 章を読んでいます。この本の「最初のテストを追加する」というタイトルのセクションで直接公開されているコードを参照しています。

背景として、この章ではAnimal、同じ名前のモジュールにサンプル クラスが作成されます。このクラスには、speak次のような単純なメソッドがあります。

sub speak {
    my $class = shift;
    print "a $class goes ", $class->sound, "!\n";
}

メソッドは特定の動物に対して返される単純な文字列です。soundたとえば、馬のsoundメソッドは単純sub sound { "neigh" }になり、そのspeakメソッドは次のように出力する必要があります。

A Horse goes neigh!

私が直面している問題は次のとおりです: ./Animal/t/Animal.t で作成したテスト コードでは、ベア ブロックを使用し、メソッドが機能Test::More::isしていることをテストするように指示されています。speakコードは、テスト ファイルで次のようになります。

[test code snip]
{
    package Foofle;
    use parent qw(Animal);

    sub sound { 'foof' }
    is( Foofle->speak, 
        "A Foofle goes foof!\n", 
        "An Animal subclass does the right thing"
    );
}

テストは失敗します。すべてのビルド コマンドを実行しましたが、「ビルド テスト」を実行すると、アニマル テストで次のエラーが発生します。

Undefined subroutine &Foofle::is called at t/Animal.t line 28.

Test::More::isプレーンの代わりに明示的に使用しようとするとis、テストはまだ失敗し、次のメッセージが表示されます。

#   Failed test 'An Animal subclass does the right thing'
#   at t/Animal.t line 28.
#          got: '1'
#     expected: 'A Foofle goes foof!
# '

私のメソッドは、私が説明したとおりに定義されているようです。最初のエラーは、むき出しのブロックによるスコープの問題だと思いますが、100% 確実ではありません。2 番目のエラーについては不明です。Foofleクラスを子として作成しAnimalて呼び出しspeakた場合、1 の応答が得られず、期待される出力が得られるためです。

誰かが私が間違っているかもしれないことを手伝ってくれるでしょうか? おそらく関連するソフトウェア バージョンとして、perl v5.16、Test::More v0.98、および Module::Starter v1.58 を使用しています。

4

3 に答える 3

5

最初のエラーの理由を正しく説明し、正しく修正しました (正しいパッケージ名を指定して)。しかし、あなたは単純な事実を見落としているようです: speakAnimal クラスのメソッドはreturnこのa $class goes...文字列ではなく、代わりにそれを出力した結果 (つまり1) を返します!

このサブルーチンを参照してください:

sub speak {
    my $class = shift;
    print "a $class goes ", $class->sound, "!\n";
}

return...明示的なステートメントはありません。この場合、返されるのは、サブルーチンの最後に呼び出されたステートメントを評価した結果print somethingです。 1true

そのため、テストは失敗します。1テストするか(しかし、それはあまりにも些細なことだと思います)、メソッド自体を変更して、出力する文字列を返すようにすることで修正できます。例えば:

sub speak {
    my $class = shift;
    my $statement = "a $class goes " . $class->sound . "!\n";
    print $statement;
    return $statement;
}

...そして、率直に言って、どちらのアプローチも少し...怪しいように見えます。後者は明らかにより完全ですが、実際にはこのspeakメソッドのすべての機能をカバーするわけではありません。ステートメントが正しいかどうかだけをテストしますが、出力されたかどうかはテストしません。)

于 2012-10-19T00:20:07.327 に答える
4

isに電話をかける際の問題は、電話をかけたときに間違ったパッケージに入っていたことであることがすでにわかっています。is名前空間へのインポートと同様に、作業を行ったときに関数名を完全に指定します。

use Test::More;

テストのパッケージのどこかにあります。

残りの質問に対する答えは、テストしていることと実行していることの違いにあります。印刷とは何ですか、しかしあなたが尋ねるときspeak あなたはそれが印刷したものとは無関係であるis(speak, ...)何が返ってくるかについて尋ねています。speak実際、これはあまり役に立たない戻り値ですprint

の目的はspeak特定の文字列を印刷することであるため、のテストspeakでは、実際に文字列を印刷したこと、およびそれが正しい文字列であることをテストする必要があります。ただし、これを行うためのテストには、印刷されたものをキャプチャするための何らかの方法が必要です。

実際、これを行うにはいくつかの方法がありIO::Fileます。印刷先のファイルハンドルを指定するように強制する方法から、クラスに置き換えをパッチするモンキーパッチまで、いくつかの方法がありprintますが、次の手法では、システムを変更する必要はありません。そのテスト容易性を改善するためにテストします。

selectビルトインにより、印刷する場所を変更できますprint。デフォルトの出力チャネルはですがSTDOUT、通常はそのようなことを知らないふりをする必要があります。幸い、select元のファイルハンドルを検出するために使用することもできますが、何らかの理由でテストが終了した場合でも、デフォルトのファイルハンドル(結局のところ、グローバル変数)を復元する必要があります。したがって、例外を管理する必要があります。また、内容を確認できるファイルハンドルが必要ですが、必ずしも実際に何かを印刷する必要はありません。IO::Scalarそこで助けることができます。

このアプローチでは、元のコードを次のようにテストできるようになります。

package AnimalTest;
use IO::Scalar;

use Test::More tests => 1;
use Try::Tiny;

{
    package Foofle;
    use base qw(Animal);

    sub sound { 'foof' }
}

{
    my $original_FH = select;
    try {
        my $result;
        select IO::Scalar->new(\$result);

        Foofle->speak();
        is(
            $result, "A Foofle goes foof!\n",
            "An Animal subclass does the right thing"
        );
    } catch {
        die $_;
    } finally {
        select $original_FH;
    };
}

Try::Tinyは、動脈瘤speakが発生した場合にゴミを捨てないようにし、実際に画面に出力するのではなく、スカラーを変更するようにリダイレクトします。そして、正しい理由でテストが失敗します。つまり、文字列の大文字と小文字が一致していません。Animalprint

多くの設定が含まれていることに気付くでしょう。これは、テスト対象のシステムがテスト容易性のために特に適切に設定されていないため、補正する必要があるためです。私自身のコードでは、これは私が選択するアプローチではなく、代わりに元のコードをよりテストしやすくすることを選択します。次に、テストのために、多くの場合TMOEを使用して、モンキーパッチを適用します(つまり、テスト対象のメソッドの1つをオーバーライドします) 。このアプローチは次のようになります。

[動物の場合:]

sub speak {
    my $class = shift;
    $class->print("a $class goes ", $class->sound, "!\n");
}

sub print {
    my $class = shift;
    print @_;
}

[後で:]

{
    package Foofle;
    use base qw(Animal);

    sub sound { 'foof' }

    sub print {
        my ($self, @text) = @_;

        return join '', @text;
    }

}

is(
    Foofle->speak(), "A Foofle goes foof!\n",
    "An Animal subclass does the right thing"
);

これは元のコードに非常によく似ていることに気付くでしょう。主な違いは、ビルトインをprint直接Animal呼び出す代わりに、を呼び出すことです。$class->printこれにより、ビルトインが呼び出されprintます。次に、サブクラスFoofleはそのメソッドをオーバーライドしてprint、引数を出力するのではなく引数を返します。これにより、テストコードは出力されたものアクセスできます。

このアプローチは、何が印刷されるかを把握するためにグローバルを変更するよりもはるかにクリーンですが、2つの欠点があります。テスト対象のコードを変更してテストしやすくする必要があり、実際に印刷が行われるかどうかをテストすることはありません。print正しい引数で呼び出されたものをテストするだけです。したがって、Animal :: printは、検査によって明らかに正しいように取るに足らないものであることが不可欠です。

于 2012-10-19T04:13:22.017 に答える
2

私はあなたのコードが次のようになると想像しています:

package SomeTest;   # if omitted, it's like saying "package main"
use Test::More;
...
{
    package Foofle;
    is( something, something_else );
}

このuse Test::Moreステートメントは、Test::Moreの関数の一部を呼び出し元の名前空間 (この場合はSomeTest(またはmain)) にエクスポートします。これは、関数が記号main::ismain::okmain::done_testingなどに対して定義されることを意味します。

で始まるブロックではpackage FoofleFoofle名前空間にいるので、Perl はシンボル に対応する関数を探しますFoofle::is。見つからないので、文句を言って終了します。

1 つの回避策は、の名前空間にもインポートTest::Moreすることです。Foofle

{
    package Foofle;
    use Test::More;
    is( something, something_else );
}

もう 1 つは、完全修飾メソッド名を使用して呼び出すことisです。

{
    package Foofle;
    Test::More::is( something, something_else );
}
于 2012-10-19T00:21:25.137 に答える