Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Prophecyを使った ユニットテスト

Prophecyを使った ユニットテスト

Avatar for ISHIDA Akio

ISHIDA Akio

April 16, 2016
Tweet

More Decks by ISHIDA Akio

Other Decks in Programming

Transcript

  1. Motivation • 布教活動 • Prophecy, PhpSpec, Behatの作者@everzet氏のファン • PHPの偉い人でもTDDの偉い人でもありません •

    あと特別英語が得意なわけでもないので独自解釈が含まれてい る可能性があります • 今日の話に興味を持った方はぜひ@everzet氏のプレゼンテー ションやBlogをチェックしてみてください
  2. 依存関係 • Bが無いとAを作れない • CとDが無いとBを作れない • どのように単体テストを行う か class A

    { function __construct(B $b) { } } class B { function __construct(C $c, D $d) { } } class C { } class D { }
  3. class B { function __construct(C $c, D $d) { }

    } class C { } class D { } class A { function __construct(B $b) { } } Outside-In/Inside-Out • Outside-In • Bをダミー(Mock, Stub)に置き換 えてAをテストする • AがBに要求しているものが明確に なる • Inside-Out • C,Dを先に実装してからBを実装す る • テストの粒度は次第に大きくなる • ダミーを実装する手間はかからな い • Bを実装中はAが存在しないので、 AがBに何を要求するかわからない
  4. Mockとは • ユニットテストを書くとき、テスト対象が依存しているオブ ジェクトの代わりとなるオブジェクトをTest Doubleという • Test DoubleにはDummy, Fake Object,

    Stub, Mock, Spyなどがある (この違いは今日は説明しません) • これらをサポートする仕組みがMocking Framework
  5. class Markdown { public function outputHtml($markdown, $writer) { // $writer->writeText(

    // MarkdownをHTMLに変換したもの // ); } } class Writer { public function writeText($text) { // 何かする } } src/Markdown.php src/Writer.php
  6. <?php class MarkdownTest extends PHPUnit_Framework_TestCase { /** @test */ public

    function 変換したhtmlを出力できること() { $writer = ???; // $writer->writeText('<p>Hi, there</p>') が // 呼ばれること $markdown = new Markdown(); $markdown->outputHtml('Hi, there', $writer); } } tests/MarkdownTest.php まず試し書き
  7. <?php class MarkdownTest extends PHPUnit_Framework_TestCase { /** @test */ public

    function 変換したhtmlを出力できること() { $writer = ???; // $writer->writeText('<p>Hi, there</p>') が // 呼ばれること $markdown = new Markdown(); $markdown->outputHtml('Hi, there', $writer); } } tests/MarkdownTest.php
  8. class FakeWriter { public function writeText($text) { $this->text = $text;

    } } Mocking Frameworkを使わずに書く 引数に何が渡されたかを後で調べるために保存 しておく
  9. $writer = new FakeWriter(); $markdown = new Markdown(); $markdown->outputHtml('Hi, there',$writer);

    $this->assertEquals( '<p>Hi, there</p>' $writer->text ); tests/MarkdownTest.php これはこれで場合によっては十分有効
  10. $writer = ???; // $writer->writeText('<p>Hi, there</p>') が // 呼ばれること $markdown

    = new Markdown(); $markdown->outputHtml('Hi, there', $writer); tests/MarkdownTest.php
  11. $writer = $this->prophesize('Writer'); $writer->writeText('<p>Hi, there</p>') ->shouldBeCalled(); $markdown = new Markdown();

    $markdown->outputHtml('Hi, there', $writer->reveal()); tests/MarkdownTest.php prophesize()メソッドで、Writerの振る舞いを記 述するためのオブジェクトを作る
  12. $writer = $this->prophesize('Writer'); $writer->writeText('<p>Hi, there</p>') ->shouldBeCalled(); $markdown = new Markdown();

    $markdown->outputHtml('Hi, there', $writer->reveal()); $writer->writeText()というメソッドを呼び出して いるように見えますがそうではなく Mockに期待する振る舞いを定義しています tests/MarkdownTest.php
  13. $writer = $this->prophesize('Writer'); $writer->writeText('<p>Hi, there</p>') ->shouldBeCalled(); $markdown = new Markdown();

    $markdown->outputHtml('Hi, there', $writer->reveal()); tests/MarkdownTest.php reveal()メソッドでモックを取り出す
  14. $ vendor/bin/phpunit tests/MarkdownTest.php F 1 / 1 (100%) Time: 85

    ms, Memory: 4.00Mb There was 1 failure: 1) MarkdownTest::test_変換したhtmlを出力できること Some predictions failed: Double¥Writer¥P1: No calls have been made that match: Double¥Writer¥P1->writeText(exact("<p>Hi, there</p>")) but expected at least one. FAILURES! Tests: 1, Assertions: 1, Failures: 1.
  15. <?php class Markdown { public function outputHtml($markdown, Writer $writer) {

    $writer->writeText( '<p>'.htmlspecialchars($markdown).'</p>' ); } } src/Markdown.php
  16. $ vendor/bin/phpunit tests/MarkdownTest.php . 1 / 1 (100%) Time: 77

    ms, Memory: 4.00Mb OK (1 test, 1 assertion)
  17. $writer = m::mock('Writer'); $writer->shouldReceive('writeText') ->with('<p>Hi, there</p>') ->once(); $markdown = new

    Markdown(); $markdown->outputHtml('Hi, there', $writer); Mockeryだとこんな感じ
  18. There was 1 error: 1) MarkdownTest::test_変換したhtmlを出力できること Prophecy¥Exception¥Doubler¥MethodNotFoundException: Method `Double¥stdClass¥P1::writeText()` is

    not defined. There was 1 error: 1) MarkdownTest::test_変換したhtmlを出力できること Prophecy¥Exception¥Doubler¥MethodNotFoundException: Method `Double¥Writer¥P1::writeText()` is not defined. Writerクラスが無い場合 writeTextメソッドが無い場合
  19. • モックと実装の食い違いを指摘できないMocking Frameworkは 壊れてるから使うな • Mockと実装との食い違いを防ぐことができる • テストがパスしたときには、Writeクラス(or インターフェー ス)のひな形が出来上がっている

    • 次にすべきことが明確になる • 「先にWriterを作っておかなければならない」ではなく「テス トに言われたからWriterを作る」と思った方がいいと思います • 一方で、Partial Mockのような機能はないので、レガシーコード のテストを書くときはMockeryの方が便利かもしれません
  20. 参考資料 • GitHub - phpspec/prophecy: Highly opinionated mocking framework for

    PHP 5.3+ https://github.com/phpspec/prophecy • thePHP.cc - PHPUnit 4.5 and Prophecy https://thephp.cc/news/2015/02/phpunit-4-5-and-prophecy • Design how your objects talk through mocking at Laracon EU 2014 • http://www.slideshare.net/everzet/design-how-your-objects-talk-through- mocking • https://www.youtube.com/watch?v=X6y-OyMPqfw • Design How Your Objects Talk Through Mocking"を見た - iakioの日 記 http://iakio.hatenablog.com/entry/2014/10/06/000000