PHPSpecのLTしてきました(+PHPUnit、Behat、Codeception記述方法の違いも)

Posted: 2015-07-28 23:11 |  PHP全般 

みなさん、テストしてますか?

第92回 PHP勉強会@東京 でPHPSpecについて簡単に紹介LTしました。
BDDってなんだ?から始めると時間がかかりすぎるため、
単純にテストフレームワークとして、PHPUnitととの書き方の違いなどの紹介だけです。
比較用のコードを書いていたら楽しくなってしまい、BehatとCodeceptionのテストも、
簡単な機能だけを使って記述しました。
ytake/spec-unit-testing
といっても見て楽しむくらいのリポジトリです。
スライド資料は大して書いていないので、このブログで少し記述の違いなどを紹介します。
(各テストフレームワークの感想は個人的な意見です)

テスト対象のメソッド

テスト対象のメソッドは、あまり書くと怒られるかもしれませんが、
「3の倍数と3のつく数字の時にahoを返す」メソッドです。 
実装コードは
    /**
     * @param $string
     * @return string
     * @throws \Exception
     */
    public function call($string)
    {
        if (is_null($string)) {
            throw new \Exception;
        }
        if (is_numeric($string)) {
            if ($string % 3 == 0 || strstr($string, '3')) {
                return 'aho';
            }
        }
        return $string;
    }
というシンプルなものです。
ちなみにこのコードを実装する時にPHPSpecを利用したものです。

PHPUnit

PHPのユニットテストで、ディファクトスタンダードでもあるPHPUnitを利用して、
このコードをテストするコードは、大まかに次のようになるはずです。
    public function testCallReturnString()
    {
        $this->assertSame('hello', $this->application->call('hello'));
    }
    /**
     * @expectedException \Exception
     */
    public function testCallThrowException()
    {
        $this->application->call(null);
    }
    public function testCallReturnResult()
    {
        $this->assertSame('1', $this->application->call('1'));
        $this->assertSame('2', $this->application->call('2'));
        $this->assertSame('aho', $this->application->call('3'));
        $this->assertSame('4', $this->application->call('4'));
        $this->assertSame('aho', $this->application->call('6'));
        $this->assertSame('10', $this->application->call('10'));
        $this->assertSame('aho', $this->application->call('13'));
    }
callメソッドに対してのテストで、それぞれ意図した動作をするかテストしています。
コードでは省いていますが、初期化としてsetUpを実行して、
通常の開発での実装と同じような感覚で簡単に利用できます。
これに対してPHPSpecです

PHPSpec

    function it_is_initializable()
    {
        $this->shouldHaveType('Acme\Crazy');
    }
    
    function it_should_return_string()
    {
        $this->call('hello')->shouldBe('hello');
    }
    
    function it_return_string_1_for_1()
    {
        $this->call('1')->shouldBe('1');
    }
    
    function it_throws_an_exception_when_arguments_are_invalid()
    {
        $this->shouldThrow('\Exception')->duringCall(null);
    }
    
    function it_return_string_3_for_aho()
    {
        $this->call('3')->shouldBe('aho');
    }
PHPSpecそのものはPSR-2などに対応していますが、テストは可読性を優先させるため、
スネークケースで記述します。RSpecを利用したことがある方はすんなり理解できると思います。
PHPUnitのsetUpに対して、PHPSpecではit_is_initializableがそれに該当します。
なお、PHPSpecの$thisはこのテストクラスの$thisではなく、
shouldHaveTypeメソッドで指定されているメソッドを指します。
PHPUnitのassertとの違いに注目してみてください。
それぞれのテストメソッドが仕様になるニュアンスが感じられると思います。
このテストでは利用していませんが、モックなどは特別な書き方をしなくても、
簡単に利用できます。
ちょっと視点の違うテストとして、Behatでも記述してみました。

Behat

Behatはフィーチャをデフォルトの英語以外に、日本語などで記述ができます。
今回はわかりやすくするために日本語で記述しました。
(パッと見、Behatが一番ダサく見えるかもしれません・・)
# language: ja
フィーチャ: 3の倍数と3が付く数字のときだけアホになります
  数字を数えながら決まった条件でahoになりたい
  ahoになると楽しそうだから
  シナリオ: 3の倍数と3が付く数字以外を数える
    前提 "1" を数えた時
    ならば "1" を数える
    前提 "4" を数えた時
    ならば "4" を数える
  シナリオ: 3の倍数を数える
    前提 "3" を数えた時
    ならば "aho" になる
    前提 "30" を数えた時
    ならば "aho" になる
  シナリオ: 3の付く数字を数える
    前提 "31" を数えた時
    ならば "aho" になる
    前提 "361478563" を数えた時
    ならば "aho" になる
多少面白おかしく書いてます
対応するコンテキストクラスは次の通りです。
    /**
     * @Given :arg1 を数えた時
     */
    public function stepDefinition1($string)
    {
        $this->string = $string;
    }
    /**
     * @Then :arg1 を数える
     */
    public function tangXiaoNaiTian($output)
    {
        expect($this->application->call($this->string))->toBe($output);
    }
    /**
     * @Then :arg1 になる
     */
    public function stepDefinition2($output)
    {
        expect($this->application->call($this->string))->toBe($output);
    }
基本的には--append-snippetsでメソッドを追加しています。
bossa/phpspec2-expectライブラリを使って簡単に記述してます。
PHPUnit、PHPSpecとのテストの視点の違いがわかると思います。
最後にCodeceptionです

Codeception

Codeceptionは受け入れテストやフレームワークと親和性が高いファンクショナルテスト、
PHPUnitを拡張したユニットテストが利用できる、多機能なテストフレームワークです。
今回はCodeceptionの受け入れテストを利用してテストコードを記述しました。
といっても、CodeceptionはWebDriverなどが簡単に利用できるように、
Domなどの操作と親和性の高いメソッドが用意されていますが、
今回は自作のメソッドを利用して記述しています。
$I = new AcceptanceTester($scenario);
$I->wantTo('perform actions and see result');
$I->m('engineer');
$I->amCalled('hello');
$I->answer('hello');
$I->amCalled('4');
$I->answer('4');
$I->lookForwardTo('want crazy');
$I->answer('aho');
$I->amCalled('13');
$I->answer('aho');
$I->amCalled('1258736984873');
$I->answer('aho');
利用したメソッドはAcceptanceTesterクラスに記述しました。
下記のようなものです。
    /**
     * @param $string
     * @throws Exception
     */
    public function answer($string)
    {
        PHPUnit_Framework_Assert::assertSame(
            $string, $this->application->call($this->number)
        );
    }
利用自体はどれも簡単です。
Codeceptionはテストファーストよりも、受け入れテストの色合いが強く感じます。
記述方法がPHPSpecなどの自然言語記法に近いスタイルを採用していますが、
テストファーストとして利用するには少し手間がかかるかもしれません。

PHPのテストもいろいろな選択肢があり、どれも使い勝手が少し違いますが、
いろいろ利用してみて、使いやすいものを取り入れてみると良いのではないでしょうか?
BDDという視点ではなくて、単純にテストフレームワークとしての記述方法の違いについて紹介しました。
個人的には最近はPHPSpec+BehatとPHPUnit併用に落ち着いています。
 

about ytake

執筆に参加しています


Laravel お役立ち情報

share



このエントリーをはてなブックマークに追加

Categories

laravel 45

DTM 0

music 0

PHP全般 31

0

JAPAN 1

WORLD 1

javascript 4

RDBMS 1

NoSQL 1

NewSQL 1

Recent Posts

Ad

comments powered by Disqus

GitHub

Social Links

Author


クリエイティブ・コモンズ・ライセンス
Yuuki Takezawa 作『Ytake Blog』はクリエイティブ・コモンズ 表示 - 非営利 4.0 国際 ライセンス で提供されています。

© ytake/comnect All Rights Reserved. 2014