Perl Hackers Hub

第51回 Test2で変わるモダンなテスト―拡張性を持ったテスティングフレームワークとTest2::V0の使い方(1)

この記事を読むのに必要な時間:およそ 5 分

データ構造の簡潔な比較

これまでは,配列やハッシュのリファレンスを渡すことでデータ構造の比較を行いました。データ構造が大きくなってくると見通しが悪くなり,さらには配列やハッシュ以外にもオブジェクトの比較がしたいということもあります。そこで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;
};

(1)itemでは,配列に含まれる要素を宣言した順に列挙しています。(3)のようにインデックスを指定することもできます。

(2)enditemで宣言された要素以外にないこと,(4)etcは宣言された要素以外にも存在し得ることを表します。これらを省略した場合,is関数のときにはendを呼び出したときと同じ挙動となりますが,わかりやすさのためどちらかを明示的に呼び出しておくことをお勧めします。

(5)では,ハッシュの比較をするために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)
};

(1)では,blessされているパッケージ名の比較を行います。

(2)では,aメソッドの戻り値の比較を行います。(3)では,DNEを渡し,bメソッドが存在しないことをチェックしています。

ちなみに,直接リファレンスを渡すのとビルダを渡すのとではどちらが良いのでしょうか。前者は記述量が少ないというメリットがありますが,後者にはarrayビルダであればすべての要素についてチェックを行うall_items関数を使うことができ,順不同による比較をbagビルダで行うといった柔軟な対応ができます。また,リファレンスを扱う場合,前者では中身の要素それぞれが宣言された行番号を取得することはできません。後者ではitemが関数の呼び出しとして実装されているため,テストの失敗時には対象となる行番号が詳細に出力されるといった良い点もあります。

isとlikeの違い

Test::Moreではlike関数は正規表現による比較を行うものでしたが,Test2::V0ではそれに加えデータ構造の比較も行えるようになっています。is関数では厳格な(strict)比較,like関数では緩やかな(relaxed)比較がされます。

この緩やかな比較とは,具体的にどのような動作をするのでしょうか。

まずはis関数,like関数での比較時のオプションについて知る必要があります。オプションには次の3種類あります。

implicit_end

比較対象の配列,ハッシュについて,余分なインデックス,キーが存在しないことをチェックする

use_regex

正規表現リテラルを直接渡すことを許可する。use_regexが無効の場合は,match関数を経由しなければならない

use_code

コードリファレンスを直接渡すことを許可する。use_codeが無効の場合は,validator関数を経由しなければならない

これらのオプションは,islike関数では表3のように対応されます。

表3 比較ルールと実装との対応

ルール(対応する関数)implicit_enduse_regexuse_code
strict(is)××
relaxed(like)×

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)
};                           

(1)にあるハッシュの比較は,(2)hashetcを使った組み合わせと同様の動作をするため,このようなケースでは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を参照してください。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.113

2019年10月24日発売
B5判/160ページ
定価(本体1,480円+税)
ISBN978-4-297-10905-9

  • 特集1
    接続エラー,性能低下,権限エラー,クラウド障害
    AWSトラブル解決
    原因調査・対応・予防のノウハウ
  • 特集2
    Ruby書き方ドリル
    要点解説と例題で身に付く!
  • 特集3
    体験
    ドメイン駆動設計
    モデリングから実装までを一気に制覇
  • 一般記事
    FigmaによるUIデザイン
    デザイナーとエンジニアがオンラインで協業できる!
  • 一般記事
    入門
    SwooleによるPHP非同期処理
    高速化のための並列実行はどのように書くのか

著者プロフィール

秋山卓巳(あきやまたくみ)

1995年北海道生まれ。YAPC::Asia 2011にて当時高校生としてYAPCスピーカーデビュー。2017年より株式会社はてなに入社後,マンガサービスに携わる。

Twitter:@akiym
URL:https://akiym.com/