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

1,809 views

Published on

PHPカンファレンス北海道2016

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,809
On SlideShare
0
From Embeds
0
Number of Embeds
1,062
Actions
Shares
0
Downloads
6
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

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

  1. 1. Prophecyを使った ユニットテスト PHPカンファレンス北海道2016 2016.4.16
  2. 2. 自己紹介 • 石田朗雄(@iakio) • フリーランス • 近所から来ました • OSC2015-Hokkaidoで 「phpspecで学ぶLondon-School-TDD」 という発表をしました
  3. 3. Motivation • 布教活動 • Prophecy, PhpSpec, Behatの作者@everzet氏のファン • PHPの偉い人でもTDDの偉い人でもありません • あと特別英語が得意なわけでもないので独自解釈が含まれてい る可能性があります • 今日の話に興味を持った方はぜひ@everzet氏のプレゼンテー ションやBlogをチェックしてみてください
  4. 4. Agenda Prophecyとは、PhpSpecのために開発され、PHPUnitにもバンドル されるようになったMocking Frameworkです • Mocking Frameworkとは • Prophecyの使い方 • 他のMocking Frameworkとの違いや注意点
  5. 5. すこしずつ前に進む • 俺が今書いたこのコード本当に動くんだろうか • 自信をもって前に進むために • 今書いたコードを今すぐ動かすためにはどうしたらいいだろう
  6. 6. 例えば単に一部を別ファイルにコピペして動か すことができるのであれば、それも立派な戦略 だが
  7. 7. 問題点 • コピペが面倒 • 未定義の変数を使っている • しかもメソッド呼んじゃってる 今日の話はこれらを解決するもうちょっといい方法、みたいな 感じです
  8. 8. TDD is not about Testing Test Driven Development テストによって駆動される開発手法
  9. 9. 開発を駆動するためにテストを使う • テスト駆動開発⇒開発手法の話 • 良いテストとは? • カバレッジは? • これらはテスト手法の話なので別のレイヤの話
  10. 10. ドライブを感じるとき • 難しい問題、複雑な問題に取り組んでいるとき • ⇒どんなクラスを作ろう • ⇒どんなメソッドを作ろう • ⇒どんな引数を渡そう
  11. 11. ぼんやり⇒くっきり • こんなクラスがあればいいな • こんなメソッドがあればいいな • こんな引数をわたせばいいな • そのオブジェクトを使う最初のコードを書いてみる(実装する 前に) • この試し書きをする場所をユニットテストと呼んでいる
  12. 12. 依存関係 • Bが無いとAを作れない • CとDが無いとBを作れない • どのように単体テストを行う か class A { function __construct(B $b) { } } class B { function __construct(C $c, D $d) { } } class C { } class D { }
  13. 13. 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に何を要求するかわからない
  14. 14. Mockとは • ユニットテストを書くとき、テスト対象が依存しているオブ ジェクトの代わりとなるオブジェクトをTest Doubleという • Test DoubleにはDummy, Fake Object, Stub, Mock, Spyなどがある (この違いは今日は説明しません) • これらをサポートする仕組みがMocking Framework
  15. 15. 例 Markdown::outputHtml($markdown, $writer) MarkdownをHTMLに変換し、ファイル等に出力するメソッド Writer::writeText($text)を呼び出す 説明のために先に実装コードをお見せします
  16. 16. class Markdown { public function outputHtml($markdown, $writer) { // $writer->writeText( // MarkdownをHTMLに変換したもの // ); } } class Writer { public function writeText($text) { // 何かする } } src/Markdown.php src/Writer.php
  17. 17. <?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 まず試し書き
  18. 18. <?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
  19. 19. class FakeWriter { public function writeText($text) { $this->text = $text; } } Mocking Frameworkを使わずに書く 引数に何が渡されたかを後で調べるために保存 しておく
  20. 20. $writer = new FakeWriter(); $markdown = new Markdown(); $markdown->outputHtml('Hi, there',$writer); $this->assertEquals( '<p>Hi, there</p>' $writer->text ); tests/MarkdownTest.php これはこれで場合によっては十分有効
  21. 21. Prophecyの使い方
  22. 22. $writer = ???; // $writer->writeText('<p>Hi, there</p>') が // 呼ばれること $markdown = new Markdown(); $markdown->outputHtml('Hi, there', $writer); tests/MarkdownTest.php
  23. 23. $writer = $this->prophesize('Writer'); $writer->writeText('<p>Hi, there</p>') ->shouldBeCalled(); $markdown = new Markdown(); $markdown->outputHtml('Hi, there', $writer->reveal()); tests/MarkdownTest.php
  24. 24. $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の振る舞いを記 述するためのオブジェクトを作る
  25. 25. $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
  26. 26. $writer = $this->prophesize('Writer'); $writer->writeText('<p>Hi, there</p>') ->shouldBeCalled(); $markdown = new Markdown(); $markdown->outputHtml('Hi, there', $writer->reveal()); tests/MarkdownTest.php reveal()メソッドでモックを取り出す
  27. 27. $ 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.
  28. 28. <?php class Markdown { public function outputHtml($markdown, Writer $writer) { $writer->writeText( '<p>'.htmlspecialchars($markdown).'</p>' ); } } src/Markdown.php
  29. 29. $ vendor/bin/phpunit tests/MarkdownTest.php . 1 / 1 (100%) Time: 77 ms, Memory: 4.00Mb OK (1 test, 1 assertion)
  30. 30. Mockeryとの違い • パーシャルモックやstatic methodのモックなどの機能は無い • Writerクラスまたはインターフェースが存在しないとpassしな い • 上記にwriteText()メソッドが存在しないとpassしない
  31. 31. $writer = m::mock('Writer'); $writer->shouldReceive('writeText') ->with('<p>Hi, there</p>') ->once(); $markdown = new Markdown(); $markdown->outputHtml('Hi, there', $writer); Mockeryだとこんな感じ
  32. 32. 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メソッドが無い場合
  33. 33. “Do not use broken mocking tools
  34. 34. • モックと実装の食い違いを指摘できないMocking Frameworkは 壊れてるから使うな • Mockと実装との食い違いを防ぐことができる • テストがパスしたときには、Writeクラス(or インターフェー ス)のひな形が出来上がっている • 次にすべきことが明確になる • 「先にWriterを作っておかなければならない」ではなく「テス トに言われたからWriterを作る」と思った方がいいと思います • 一方で、Partial Mockのような機能はないので、レガシーコード のテストを書くときはMockeryの方が便利かもしれません
  35. 35. さいごに • 少しずつ前に進む • 小さな部品を組み合わせて大きなプログラムを作る • そのための手段としてのMockとユニットテスト • 何故なら、プログラミングは未知との遭遇の連続だから • あと、興味を持った方は ”Design How Your Objects Talk Through Mocking at Laracon EU 2014 ” をCheck
  36. 36. 参考資料 • 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
  37. 37. ご清聴 ありがとうございました

×