js には Power Assert という便利なパッケージがあります。私自身は js でテストをほとんど書いたことがないので使ったこと無いのですが、例えば次のようなアサーションが、
assert(ary.indexOf(zero) === two)
次のように assert
の各部位の値が表示されるようになります(README より)。
assert(ary.indexOf(zero) === two) | | | | | | | | | 2 | -1 0 false [1,2,3] [number] two => 2 [number] ary.indexOf(zero) => -1
通常のアサーションライブラリでは大量のアサーション API があり、わかりやすい出力を得るためには API を使い分ける必要があります。Power Assert ならだいたい assert
ひとつで上記のようなわかりやすい出力が得られるため、大量の API を覚える必要が無いメリットがあります。
これはソースコードの AST を書き換えて assert
を拡張することで実現されているのですが、今日日の PHP ならソースコードを解析して AST を得るのは容易なので、同じようなものを PHP で作ってみました。
Phpower
Composer でインストールできます。重要な点として PHPUnit も Composer でインストールする必要があります。
composer require --dev phpunit/phpunit ngyuki/phpower:dev-master
例えば次のようなテストコードが、
<?php namespace Test; use PHPUnit\Framework\TestCase; class SomeTest extends TestCase { public function test() { $ary = [1,2,3]; $zero = 0; $two = 2; assert(array_search($zero, $ary, true) === $two); } }
次のように出力されます。
Assertion failed array_search($zero,$ary,true) === $two -> false # $zero -> 0 # $ary -> [ # 1, # 2, # 3, # ] # array_search($zero,$ary,true) -> false # $two -> 2
Power Assert のように罫線を引きたいところですが簡単ではなさそうなので上のような出力になっています。値の表示には symfony/var-dumper を使っています。配列が要素ごとに改行されるので縦幅がちょっと冗長な気もしますね。
いくつか制限もあります。今判っている限りでは次のような制限があります。
phpunit.phar は未サポート
PHPUnit はテストコードを読み込むときに PHPUnit\Util\FileLoader
というクラスを使っています。PHPUnit がこのクラスをロードする前に同名のクラスを先に定義することで PHPUnit\Util\FileLoader
の実装を差し替えて、テストコードが読み込まれるときにコードの書き換えを行っています。
PHPUnit\Util\FileLoader
の差し替え版の読み込みは composer.json
の autoload.files
で行うようにしているので vendor/autoload.php
が読まれた時点で差し替えが行われます。Composer で入れた PHPUnit であれば vendor/bin/phpunit
の中で一番最初に vendor/autoload.php
が読まれるのでこれは有効に働きます。
しかし、phpunit.phar
の場合、phpunit.phar
のスタブコードで phpunit.phar
に含まれるコードが一括で読まれています。そこに PHPUnit\Util\FileLoader
も含まれているため差し替えることができず、テストコードを書き換えることができません。
ので、phpunit.phar
で実行した場合は assert
はただの assert として機能します。
リファレンス引数の関数呼び出し
リファレンス引数を持つ関数呼び出しが含まれると元の assert
と互換性がなくなります。
例えば次のようなコードです。
function f(&$r)
{
return ++$r;
}
$a = 0;
assert(f($a)); // Notice: Only variables should be passed by reference
assert($a === 1);
これは assert
の中身を次のように書き換えているためです(簡略化しています、実際のものとは異なります)。
_expr(_cap(f(_cap($a))))
f(_cap($a))
のように、リファレンス引数であるはずの f
関数の引数が _cap
関数の戻り値になるため Only variables should be passed by reference
となり、関数に $a
の参照が渡らなくなります。
さいごに
assert
だけで式の中の途中経過まですべて表示されるのは便利だと思う反面、PHPUnit の assertSame
などに配列を渡せば expected と actual の diff が表示されるので、どこが間違っているか直ぐに把握できて便利です。
Phpower だと差分にはならないし、また、情報量も無駄に多くなりがちなので、どこが間違っているかをぱっと見で把握するのはなかなか困難です。
ので、アサーションを全部置き換えるようなことはできず、結局使い分けは必要でしょう。もっとも、まだ作ったばかりでどの程度使えそうか自分でも判らないので、とりあえずどこかのプロジェクトに突っ込んでみて使用感を見つつ改善などしていこうと思います。