本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはakiymこと秋山卓巳さんで、
本稿のサンプルコードは、
Perlでのテスト
Perlではテストを書く際に、Test::More
というモジュールが広く使われています。これは、
本稿のテーマであるTest2は、Test::More
などのバックエンドとして使われているTest::Builder
を置き換えるべく開発されているテスティングフレームワークです。2015年ごろよりChad Granum氏によって作られています。
Test::Builderの問題点
Test::Builder
は、
Test::Builder
の問題として、Test::Builder
の関数を直接上書きするモンキーパッチによる対応でしか行えない状況でした。
この問題を解決するため、
ちなみに、Test::Builder
は今後どうなるのでしょうか。メンテナンスされないまま、Test::Builder
の内部ではTest2のAPIを呼び出すように書きなおされており、
Test2にすると変わること
Test2は、Test::Builder
やTest::More
が含まれるTest-Simple
ディストリビューションのバージョン1.
気になるのは、Test::More
をはじめとするTest::Builder
を使った既存のテストモジュールが壊れることがないように、Test::Builder
をモンキーパッチによって拡張していた場合には正しく動作しないことがあります。また、Test2::Transion
を参照してください。
Test2::V0を使ったテスト
ここからは、Test2::V0
を使ったテストを紹介します。Test2::V0はTest2
の作者によって作られた、Test2-Suite
ディストリビューションに含まれるモジュールです。Test2はフレームワークであるため基本的な機能しか提供されていませんが、Test2::V0
では一歩踏み込んだ機能が実装されています。Test::More
で提供されるテスト関数が使えるのはもちろんのこと、
まずはcpanm
コマンドにより必要なモジュールをインストールします。Test2::V0
をインストールすると、Test-Simple
、Test2-Suite
ディストリビューションに含まれるモジュールも一緒にインストールされます。
$ cpanm Test2::V0
なお、
use Test2::V0
と書くことで、
実際にビルトイン関数の動作を確かめる単純なテストを書いてみます。ここでは、Test2::V0
のo
k関数を使って、
use Test2::V0;
ok ucfirst('foo') eq 'Foo',
'先頭の文字がuppercaseになっている';
ok uc('foo') ne 'foo', 'fooとは一致しない';
done_testing;
※ これ以降に登場するコードでは、use Test2::V0;
は省略します
テストを実行すると次の結果が出力され、
$ perl ucfirst.t
# Seeded srand with seed '20180625' from local date.
ok 1 - 先頭の文字がuppercaseになっている
ok 2 - fooとは一致しない
1..2
比較の基本
あるコードが正しく動作しているかをテストするために、ok
関数に渡していました。is
関数を使うと、
is ucfirst('foo'), 'Foo';
is [1..3], [1, 2, 3], '配列の比較';
is {map { ($_ => 1) } qw(a b)},
{a => 1, b => 1}, 'ハッシュの比較';
もしコードが意図したとおりに動作していない場合、
is ucfirst('foo'), 'bar';
is [1..3], [1, 2, 0], '配列の比較';
is {map { ($_ => 1) } qw(a b)},
{a => 1, c => 1}, 'ハッシュの比較';
図1の実行結果を見ると、Test::More
に比べると読みやすい形式になっています。
$ perl compare-fail.t # Seeded srand with seed '20180625' from local date. not ok 1 # Failed test at compare-fail.t line 3. # +-----+----+-------+ # | GOT | OP | CHECK | # +-----+----+-------+ # | Foo | eq | bar | # +-----+----+-------+ not ok 2 - 配列の比較 # Failed test '配列の比較' # at compare-fail.t line 4. # +------+-----+----+-------+ # | PATH | GOT | OP | CHECK | # +------+-----+----+-------+ # | [2] | 3 | eq | 0 | # +------+-----+----+-------+ not ok 3 - ハッシュの比較 # Failed test 'ハッシュの比較' # at compare-fail.t line 5. # +------+------------------+---------+------------------+ # | PATH | GOT | OP | CHECK | # +------+------------------+---------+------------------+ # | {c} | <DOES NOT EXIST> | | 1 | # | {b} | 1 | !exists | <DOES NOT EXIST> | # +------+------------------+---------+------------------+ 1..3
ここまで、Test2::V0
では、
値の比較
is
関数に渡した値はデフォルトで文字列による比較がされますstring
、number
、bool
関数を使います
関数名 | 説明 | 比較時の演算子 |
---|---|---|
string | 文字列による比較 | $input eq $got |
number | 数値による比較 | $input == $got |
bool | 真偽値による比較 | ($input xor $got) ? 0 : 1 |
また、!
演算子を使って否定した場合の真偽値の扱われ方については注意が必要です。必要に応じて、number
やbool
関数を使い分けるとよいでしょう。
is lc('FOO'), string 'foo';
is '1.0', number 1;
isnt '1.0', 1; # 文字列による比較になるため両者は異なる
is !!0, bool 0;
is !!0, ''; # ここで期待している真偽値は空文字列になる
正規表現と条件式による比較
正規表現にマッチする文字列であることをテストする場合は、matc
h関数を使います。また、validator
関数を使います。コードリファレンスを渡し、
is 'abc', match qr/^[a-z]+$/, 'すべてが小文字';
is 1, validator(sub { $_ > 0 }), '正の整数';
比較のショートカット
比較するデータ構造の要素が増えてくると、Test2::V0
には、
関数名 | 説明 | データ例 |
---|---|---|
T | 真偽値によるチェックの結果、 | 1、 |
F | 真偽値によるチェックの結果、 | 0、 |
D | 値がundefではない | 1、 |
U | 値がundefである | undef |
DF | 値がdefinedであるが真偽値による評価では偽である | 0、 |
E | 配列、 | {a => 1}は{a => E}にマッチ |
DNE | 配列、 | {a => 1}は{b => DNE}にマッチ |
FDNE | 配列、 | {a => 0}は{a => FDNE, b => FDNE}にマッチ |
データ構造の簡潔な比較
これまでは、Test2::V0
には、
ビルダによるデータ構造の比較
ビルダでは、array
ビルダ、hash
ビルダを使って行います。
is [1, 2], array {
item 1; ┓(1)
item 2; ┛
end; ――(2)
};
is [3, 4, 5], array {
item 1 => 4; ――(3)
etc; ――(4)
};
is {a => 1, b => 2}, hash {
field a => 1; ――(5)
etc;
};
item
では、
end
はitem
で宣言された要素以外にないこと、etc
は宣言された要素以外にも存在し得ることを表します。これらを省略した場合、is
関数のときにはend
を呼び出したときと同じ挙動となりますが、
field
により対応するキーとバリューを列挙しています。
また、object
ビルダによって比較できます。
package Foo {
sub a { $_[0]->{a} }
}
my $foo = bless { a => 1 }, 'Foo';
is $foo, object {
prop blessed => 'Foo'; ――(1)
call a => 1; ――(2)
call b => DNE; ――(3)
};
bless
されているパッケージ名の比較を行います。
a
メソッドの戻り値の比較を行います。DNE
を渡し、b
メソッドが存在しないことをチェックしています。
ちなみに、arra
yビルダであればすべての要素についてチェックを行うall_
関数を使うことができ、bag
ビルダで行うといった柔軟な対応ができます。また、item
が関数の呼び出しとして実装されているため、
isとlikeの違い
Test::More
ではlike
関数は正規表現による比較を行うものでしたが、Test2::V0
ではそれに加えデータ構造の比較も行えるようになっています。is
関数では厳格なlike
関数では緩やかな
この緩やかな比較とは、
まずはis
関数、like
関数での比較時のオプションについて知る必要があります。オプションには次の3種類あります。
- implicit_
end 比較対象の配列、
ハッシュについて、 余分なインデックス、 キーが存在しないことをチェックする - use_
regex 正規表現リテラルを直接渡すことを許可する。
use_
が無効の場合は、regex match
関数を経由しなければならない- use_
code コードリファレンスを直接渡すことを許可する。
use_
が無効の場合は、code validator
関数を経由しなければならない
これらのオプションは、is
とlike
関数では表3のように対応されます。
ルール | implicit_ | use_ | use_ |
---|---|---|---|
strict | ○ | × | × |
relaxed | × | ○ | ○ |
like
関数を使い、
like 'foo', qr/f/;
like 2, sub { $_ % 2 == 0 };
like {a => 1, b => 2}, {b => 2}; ――(1)
is {a => 1, b => 2}, hash { ┓
field b => 2; |
etc; |(2)
}; ┛
hash
とetc
を使った組み合わせと同様の動作をするため、like
関数を使うことで簡潔に記述できます。
否定するテスト
テストを書いていると、
このような場合のテストを表現するには、
ok $x != 100;
isnt $x, 100;
1行目では、$x
の値がテスト関数側に渡っていないため、
2行目では、is
関数の代わりにisnt
関数を使っています。テストが失敗したときには$x
の値が出力されますが、
そのため、is
関数とlike
関数では比較条件の否定が表現できるようになっています。比較を行う関数では否定演算子!
がオーバーロードされており、
is {a => 'foo'},
{a => !string 'bar'}, '文字列barではない';
ここまで、Test2::Tools::Compare
を参照してください。
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT