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

はむはむエンジニアぶろぐ

このブログのコンセプトは"ハッキングの為なら愛する家族を傷つけることをいとわない" 自分にとってエンジニアリングは "手段ではなく生きる目的" である

PHPのMatcherライブラリPHP Matcherを使ってみる

PHP PHPUnit テスト OSS

f:id:secret_hamuhamu:20150404170600p:plain
PHP MatcherというPHP用のマッチャーを見つけたので、使ってみた。
PHPUnitなどのテスティングツールと合わせて使うことができる。

マッチャーとは?

  • テストの表現力を高めることができる
  • アサーションの条件をより詳しくかける


マッチャーを使うことで、複数のアサーションでしか記述できなかったものを一つのアサーションでかけるようになったりします。
一つのテストメソッド(テストケース)で、複数のアサーションが出現することは、あまりよいといえないです。


それを全てマッチャーで解決できるといえばNOですが、使うメリットはあります。
そもそも、一つのテストケースで、やりたいことが多すぎる場合など。

ということで、PHP Matcherの使い方を紹介。

環境構成

  • PHP 5.6.13
  • PHPUnit 4.8.6

PHP Matcherの使い方

URLはこちらPHP Matcher

READMEがちょっとおもしろいw

PHP Matcher lets You assert like a gangster in Your test cases, where response can be something you cannot predict

PHP Matcherを使うことで、ギャングのようにassertできるらしい。

インストール

composer require --dev "coduo/php-matcher"

PHPUnitと組み合わせてみる

PHPUnitと組み合わせたサンプルコード。

<?php
use Coduo\PHPMatcher\Factory\SimpleFactory;
class MatcherTest extends PHPUnit_Framework_TestCase
{
    private $matcher;
    public function setUp()
    {
        $factory = new SimpleFactory();
        $this->matcher = $factory->createMatcher();
    }

    /** @test */
    public function 文字列であること()
    {
        $isMatch = $this->matcher->match('こんにちわ', '@string@');
        $this->assertTrue($isMatch);

        $isMatch = $this->matcher->match('1000', '@string@');
        $this->assertTrue($isMatch);
    }

    /** @test */
    public function 文字列ではないこと()
    {
        $isMatch = $this->matcher->match(100, '@string@');

        $this->assertFalse($isMatch);
    }

    /** @test */
    public function integerであること()
    {
        $isMatch = $this->matcher->match(100, '@integer@');
        $this->assertTrue($isMatch);

        $isMatch = $this->matcher->match('100', '@integer@');
        $this->assertTrue($isMatch);
    }

    /** @test */
    public function integerではないこと()
    {
        $isMatch = $this->matcher->match(1.00, '@integer@');
        $this->assertFalse($isMatch);
    }

    /** @test */
    public function exprを使ったテスト()
    {
        $isMatch = $this->matcher->match(new \DateTime('2014-04-01'), "expr(value.format('Y-m-d') == '2014-04-01')");
        $this->assertTrue($isMatch);

        $isMatch = $this->matcher->match(new Hoge(), "expr(value.hoge() == 'hoge')");
        $this->assertTrue($isMatch);

        // 9より大きいこと
        $isMatch = $this->matcher->match(10, "expr(value > 9)");
        $this->assertTrue($isMatch);
    }

    /** @test */
    public function 便利な使い方()
    {
        $isMatch = $this->matcher->match(
                'こんにちわ おはよう こんばんわ',
                "@string@.startsWith('こんにちわ').contains('おはよう').endsWith('こんばんわ')"
            );
        $this->assertTrue($isMatch);

        // 200未満10より大きい数値
        $isMatch = $this->matcher->match(11, '@integer@.lowerThan(200).greaterThan(10)');
        $this->assertTrue($isMatch);

        // 内部実装としては、new \DateTime($value)で、例外が発生しなければtrueとなっている
        // 割とゆるい比較でこういうありえない日付「2015-01-0A」に対してもtrueを返すので注意
        $isMatch = $this->matcher->match(
                '2015-01-01',
                "@string@.isDateTime()"
            );
        $this->assertTrue($isMatch);
    }
}

class Hoge {
    public function hoge()
    {
        return 'hoge';
    }
}

こんな感じ。
1テストメソッドに複数assert使ってますが、サンプルなので気にしないでください。


READMEを読むなりソースコードを読んでもらえればわかりますが、他にもいろいろできます。
マッチャーを使うメリットは、なんといっても テストの表現力 が増すこと。


テストコードにコメントを付けて何をテストしたいのか?説明しなくてもテストコード自体が十分に説明できている。

マッチャーを使った場合

$isMatch = $this->matcher->match(11, '@integer@.lowerThan(200).greaterThan(10)');
$this->assertTrue($isMatch);

マッチャーを使わない場合

$value = 11;
$this->assertTrue(is_int($value));
$this->assertTrue($value < 200);
$this->assertTrue($value > 10);

マッチャーを使わない場合でも、読めなくはないがマッチャーを使ったほうが分かりやすいのは間違いないだろう。


PHP Matcherへの不満

すごくいいライブラリですが、ちょっと不満あります。
不満というより、もっとこうなればいいのになぁ〜と言った感想。
pull reqestを送れやって話ですけどw

比較がゆるい

isDateTime など比較がゆるい物があります。
もっと厳密に比較したい場合は、使えないです。

文字列リテラルで条件を書くこと

文字列リテラルで、条件をこんなふうに書いていきますが、

"expr(value.format('Y-m-d') == '2014-04-01')")

綴がミスっていた場合は、 false が返ります。
例えば、最後の括弧閉じを忘れていた場合

"expr(value.format('Y-m-d') == '2014-04-01'")

タイポをエンジニアに発見させるのは、辛いと思うので条件式は、専用のメソッドを用意するなりしてタイポに気づけるようになってほしい。
私のイメージ

$this->expr("format('Y-m-d')", $this->equal(), '2014-04-01')

もっといいインターフェースは、あると思うけど。

まとめ

1つのテストケースにアサーションが複数登場した際は、立ち止まる必要がある。

そもそも、テストの単位が大きすぎないか?
アサーションの条件が複雑な場合は、マッチャーを使ってみるといいのではないか?

おすすめの本

パーフェクトPHP

パーフェクトPHP

PHPはどのように動くのか ?PHPコアから読み解く仕組みと定石

PHPはどのように動くのか ?PHPコアから読み解く仕組みと定石

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)