読者です 読者をやめる 読者になる 読者になる

Test::More、Test::Classを使ったテスト

perl テスト

Test::More、Test::Classをそれぞれ使ったテストについて調べました。


テストするモジュールは、下記のようなものです。

# 直線を表すモジュール
package Line;
use strict;
use warnings;


# 「ax + by + c = 0」の「a,b,c」部分が渡される
sub new {
    my $class = shift;
    my ($a, $b, $c) = @_;
    bless { a => $a, b => $b, c => $c }, $class;
}

# 「ax + by + c = 0」の「a,b,c」部分を表示
sub to_string {
    my $self = shift;
    return "a = " . $self->{a} . ", b = " . $self->{b} . ", c = " . $self->{c};
}

# 渡された直線との交点を返す
sub get_intersection_point {
    my $self = shift;
    my ($l) = @_;

    my $d = $self->{a} * $l->{b} - $l->{a} * $self->{b};

    if ($d == 0.0) {
        return undef;
    }

    my $x = ($self->{b} * $l->{c} - $l->{b} * $self->{c}) / $d;
    my $y = ($l->{a} * $self->{c} - $self->{a} * $l->{c}) / $d;
    return { x => $x, y => $y };                                                                        
}                                                                                                       
                                                    

# 2点の座標(x1, y1) (x2, y2)から直線インスタンスを作成する
sub from_points {                                                                                       
    my ($x1, $y1, $x2, $y2) = @_;                                                                       
    my $dx = $x2 - $x1;
    my $dy = $y2 - $y1;
    return Line->new($dy, -$dx, $dx * $y1 - $dy * $x1);
}

1;


「ax + by + c = 0」の直線を表すモジュールです。
モジュールは「a,b,c」の値を保持していて、一つの直線を表します。
このモジュールで算出されるはずの座標は、手計算であらかじめ算出しておきます。

Test::More

下記が一連のテストの記述です。

# 00.t
use strict;
use Test::More;
plan( tests => 8 );

# モジュール読み込みチェック
use_ok("Line");

# インスタンスの確認
my $obj = Line::from_points(0, 1, 6, 4);
isa_ok( $obj, 'Line' );

# メソッドが定義されているかのチェック
can_ok( $obj, qw/new to_string from_points get_intersection_point/);

# 期待値かどうか
is( $obj->{a}, 3, '$obj->{a}が期待値と一致するか確認');
is( $obj->{b}, -6, '$obj->{b}が期待値と一致するか確認');
is( $obj->{c}, 6, '$obj->{c}が期待値と一致するか確認');


# 正規表現でチェック
like( $obj->to_string(), qr/a = [\.\-0-9]+, b = [\.\-0-9]+, c = [\.\-0-9]+/, '$obj->to_string()が正規表現と一致するか確認');


my $obj2 = Line::from_points(3, 6, 5, 0);
my $point = $obj->get_intersection_point($obj2);


# 構造体が一致しているか
is_deeply( $point, { x => 4, y => 3 }, '$pointが構造体と一致するか確認' );


上記のプログラムは「00.t」という名前で保存しました。
このテストを実行するために、proveコマンドを使用します。
proveコマンドは、「Test::Harness」をインストールすると付いてきます。


「prove -lv t/00.t」で実行すると下記のようにテストが完了します。

$ prove -lv t/00_compile.t
Name "main::running_under_some_shell" used only once: possible typo at /usr/bin/prove5.10.0 line 3.
t/00_compile.t .. 
1..8
ok 1 - use Line;
ok 2 - The object isa Line
ok 3 - Line->can(...)
ok 4 - $obj->{a}が期待値と一致するか確認
ok 5 - $obj->{b}が期待値と一致するか確認
ok 6 - $obj->{c}が期待値と一致するか確認
ok 7 - $obj->to_string()が正規表現と一致するか確認
ok 8 - $pointが構造体と一致するか確認
ok
All tests successful.
Files=1, Tests=8,  0 wallclock secs ( 0.06 usr  0.01 sys +  0.05 cusr  0.02 csys =  0.14 CPU)
Result: PASS

Test::Class

Test::Classは、xUnit風にテストが書けるフレームワークです。
オブジェクト指向に書けるというだけで、Test::Moreと同じほとんど同じ感じです。

#!/usr/bin/env perl                                                                                     
use strict;
use warnings;

My::Test->runtests;


package My::Test;
use base qw/Test::Class/;
use Test::More;
use Line;

sub make_fixture : Test(setup) {
    my $self = shift;
    $self->{line} = Line::from_points(0, 1, 6, 4);
}

sub test_instance : Test(no_plan) {
    my $line = shift->{line};

    isa_ok $line, 'Line';
    ok $line->can($_) for qw/new to_string from_points get_intersection_point/;
}

sub test_member : Test(no_plan) {
    my $line = shift->{line};

    is $line->{a}, 3, '$obj->{a}が期待値と一致するか確認';
    is $line->{b}, -6, '$obj->{b}が期待値と一致するか確認';
    is $line->{c}, 6, '$obj->{c}が期待値と一致するか確認';
}

sub test_method : Test(no_plan) {
    my $obj = shift->{line};

    like  $obj->to_string(), qr/a = [\.\-0-9]+, b = [\.\-0-9]+, c = [\.\-0-9]+/, '$obj->to_string()が正規表現と一致するか確認';

    my $line2 = Line::from_points(3, 6, 5, 0);
    my $point = $obj->get_intersection_point($line2);

    is_deeply  $point, { x => 4, y => 3 }, '$pointが構造体と一致するか確認';                            
}                                                                                                       
                                                                                                        
1;                                                                                                      


関数の属性で「: Test(no_plan)」と書きます。
「no_plan」の場所は、テストの回数を書きますが、面倒くさいのでこうしてしまっています。
本当はいけないらしい・・・?

参考

モダンPerl入門 (CodeZine BOOKS)

モダンPerl入門 (CodeZine BOOKS)