Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

phpspecで学ぶLondon School TDD

5,826 views

Published on

オープンソースカンファレンス2015 Hokkaidoでの発表資料です

Published in: Technology
  • Be the first to comment

phpspecで学ぶLondon School TDD

  1. 1. phpspecで学ぶ London School TDD オープンソースカンファレンス2015 HOKKAIDO 2015.6.13
  2. 2. 最初に告知 Sapporo.php勉強会やります 7/17(金) 19:00~21:00 インフィニットループ会議室 http://sapporophp.connpass.com/ https://github.com/sapporophp/sapporophp こんな勉強会があったらいいなといったご意見はgithubまで
  3. 3. 自己紹介 石田朗雄 http://iakio.hatenablog.com/ https://twitter.com/iakio http://qiita.com/iakio http://www.slideshare.net/iakio
  4. 4. Motivation PHPの偉い人でもTDDの偉い人でもありません あと特別英語が得意なわけでもありません でもphpspecもLondon School TDDも日本ではあまり情報量が多くな い(海外のPHPコミュニティでは度々話題になっている模様) 本当はこういう話を自分で話すんじゃなく誰かにしてほしい ガチ勢から見ると突っ込みどころあるかもしれませんが出来るだ け自分の言葉で説明したいと思います 最後に沢山参考資料へのリンクを付たよ お客様の中で London PHPコミュ ニティの方はい らっしゃいますか
  5. 5. London school TDD ロンドンのXPコミュニティが発祥 モックを積極的に使い、状態ではなく振る舞いに注目したTDDの 手法 Growing Object-Oriented Software Guided By Tests(2009) 通称The GOOS Book 実践テスト駆動開発(2012) phpspecの作者「London school TDDを学ぶ上でベストなリソース」 (Full Stack Radio #15) ただし結構難しい本 ◦ GUIあり、ネットワーク通信あり、マルチスレッドあり ◦ 難しい問題を解決する手法を解説しようとすると、例も難しくなると いう技術書のジレンマ
  6. 6. 個人的な体験として The GOOS Bookを読む → 「なるほど(わからん)」 phpspecを知る → 「なるほど(わからん)」 phpspecについて調べる → 「それThe GOOS Bookに書いてあるよ」 The GOOS Bookを読む → 「ホンマや!」 💡 phpspecを学ぶこ とでLondon School TDDが理解できた (気がする) phpspecを使えば London School TDD を上手く説明でき るのでは?
  7. 7. 俺は単体テストが書きたい だけなんだ (可能であれば)大きなプログラムより小さなプログラムの方が テストは簡単なはず テストが失敗した場合も原因の特定が簡単なはず 今書いたコードを今すぐ動かして確認したい
  8. 8. 依存関係 Bが無いとAを作れない CとDが無いとBを作れない どのように単体テストを行うか class A { function __construct(B $b) { } } class B { function __construct(C $c, D $d) { } } class C { } class D { }
  9. 9. Outside-In/Inside-Out class A { function __construct(B $b) { } } class B { function __construct(C $c, D $d) { } } class C { } class D { } Outside-In ◦ Bをダミー(Mock, Stub)に置き換え てAをテストする ◦ AがBに要求しているものが明確にな る Inside-Out ◦ C,Dを先に実装してからBを実装する ◦ テストの粒度は次第に大きくなる ◦ ダミーを実装する手間はかからない ◦ Bを実装中はAが存在しないので、A がBに何を要求するかわからない
  10. 10. モックを積極的に使う あるメソッドを呼び出したとき、 最終的には値を返すか他のオブ ジェクトのメソッドを呼び出し ているはず テスト対象のオブジェクトと、 関連するオブジェクトの間のや り取りだけ確認できれば、テス ト対象のオブジェクト内で起き ていることは気にする必要は無 いのでは?(プライベートメ ソッド、プライベート変数) もう全部モックでいいんじゃな いかな Mock Mock Mock Mock
  11. 11. phpspec 大雑把にいえばPHPUnitのようなもの(だいぶ違う) 大雑把にいえばRSpecのようなもの(だいぶ違う) phpspec(ver1)は2011年ごろからあった(RSpecを参考にしたも のだったらしい) 2013年ごろ0から書き直され、2014年にver2.0.0がリリースされる 現在の最新は2.2.1 2013年以前のphpspecに関する情報は全く別のものだと思ってくだ さい
  12. 12. Spec, Example class MarkdownSpec extends ObjectBehavior { } function it_is_initializable() { $this->shouldHaveType('Markdown'); } function its_xxxxxx() { .... } そのオブジェクト をどのように使う かという例を集め たものがSpecとな る
  13. 13. Example=例 どのように使うの か、という例を書 くつもりで
  14. 14. デモ ここからしばらく自分用のカンペ(あるいはスライド公開用)
  15. 15. { "require-dev": { "phpspec/phpspec": "~2.2" }, "autoload": { "psr-4": { "": "src/" } } } 標準構成ではソースをsrc/、スペックをspec/に 配置します。それ以外の場合や.phpspec.ymlで 設定が必要です PSR-0でも問題ありません composer.json
  16. 16. Getting Started MarkdownをHTMLに変換するMarkdownクラスのSpecを例に説明 します といっても"Hi, there"を"<p>Hi, there</p>"に変換するだけ 公式ドキュメントと同じサンプルですいません
  17. 17. C:¥Users¥ishida¥src¥phpspec>vendor¥bin¥phpspec desc Markdown Specification for Markdown created in C:¥Users¥ishida¥src¥phpspec¥spec¥MarkdownSpec.php. MarkdownクラスのSpecのひな形を作成します phpspecコマンドを使わずに作ってもよいです
  18. 18. <?php namespace spec; use PhpSpec¥ObjectBehavior; use Prophecy¥Argument; class MarkdownSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('Markdown'); } } spec/MarkdownSpec.php MarkdownクラスのSpecはMarkdownSpecとなり ます。各exampleメソッドはit_又はits_から始ま ります
  19. 19. C:¥Users¥ishida¥src¥phpspec>vendor¥bin¥phpspec run Markdown 10 - it is initializable class Markdown does not exist. / skipped: 0% / pending: 0% / passed: 0% / failed: 0% / broken: 100% / 1 examples 1 specs 1 example (1 broken) 24ms Do you want me to create `Markdown` for you? [Y/n] Class Markdown created in C:¥Users¥ishida¥src¥phpspec¥src¥Markdown.php. / skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% / 1 examples 1 specs 1 example (1 passed) 50ms スペックを実行すると、スペックの対象である Markdownクラスが無いので作りますか?と聞 かれます
  20. 20. <?php class Markdown { } src/Markdown.php 空のMarkdowクラスができます
  21. 21. ... class MarkdownSpec extends ObjectBehavior { function it_converts_plain_text_to_paragraph() { // $this->toHtml("Hi, there")が // "<p>Hi, there</p>" を返却すること } } spec/MarkdownSpec.php
  22. 22. ... class MarkdownSpec extends ObjectBehavior { function it_converts_plain_text_to_paragraph() { $this->toHtml("Hi, there") ->shouldReturn("<p>Hi, there</p>"); } } spec/MarkdownSpec.php shouldBe, shouldBeEqualTo, shouldEqualToとも書 けます
  23. 23. C:¥Users¥ishida¥src¥phpspec>vendor¥bin¥phpspec run / skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% Markdown 15 - it converts plain text to paragraph method Markdown::toHtml not found. / skipped: 0% / pending: 0% / passed: 50% / failed: 0% / broken: 50% / 2 examples 1 specs 2 examples (1 passed, 1 broken) 32ms Do you want me to create `Markdown::toHtml()` for you? [Y/n] Method Markdown::toHtml() has been created. / skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% Markdown 15 - it converts plain text to paragraph expected "<p>Hi, there</p>", but got null. / skipped: 0% / pending: 0% / passed: 50% / failed: 50% / broken: 0% / 2 examples 1 specs 2 examples (1 passed, 1 failed) 38ms toHtml()メソッドがないので作りますか?
  24. 24. C:¥Users¥ishida¥src¥phpspec>vendor¥bin¥phpspec run --fake / skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% Markdown 15 - it converts plain text to paragraph expected "<p>Hi, there</p>", but got null. / skipped: 0% / pending: 0% / passed: 50% / failed: 50% / broken: 0% / 2 examples 1 specs 2 examples (1 passed, 1 failed) 67ms Do you want me to make `Markdown::toHtml()` always return '<p>Hi, there</p>' for you? [Y/n] Method Markdown::toHtml() has been modified. / skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% / 2 examples 1 specs 2 examples (2 passed) 27ms toHtml()メソッドが常に"<p>Hi, there</p>"を返す ようにしますか?
  25. 25. <?php class Markdown { public function toHtml($argument1) { return '<p>Hi, there</p>'; } } src/Markdown.php 仮実装ができました。
  26. 26. Point MarkdownクラスのSpecはMarkdownSpecクラスに書く 各exampleのメソッド名は、'it_'または'its_' expectationは$thisに対して書く つまりMarkdownSpecの中で$thisはMarkdownクラスのインスタン スをラップしたオブジェクトとなる このため、1つのSpecには1つのクラスのテストしか書けない phpspec desc クラス名で、Specのひな形を作る phpspec run --fakeで、仮実装を作る
  27. 27. スタブ ファイル等から読み出したMarkdownをHTMLに変換するメソッド Markdown::toHtmlFomReader(Reader $reader) $reader->getMarkdown()を呼び出し、取得したMarkdownをHtmlに 変換する 指定した値を返却するダミーのReaderオブジェクトを作成する (スタブ)
  28. 28. class MarkdownSpec extends ObjectBehavior { ... function it_convers_text_from_an_external_source() { // $reader->getMarkdown()が"Hi, there"を返すとする $this->toHtmlFromReader($reader) ->shouldReturn("<p>Hi, there</p>"); } spec/MarkdownSpec.php
  29. 29. class MarkdownSpec extends ObjectBehavior { ... function it_convers_text_from_an_external_source() { $reader->getMarkdown()->willReturn("Hi, there"); $this->toHtmlFromReader($reader) ->shouldReturn("<p>Hi, there</p>"); } spec/MarkdownSpec.php
  30. 30. use Reader; class MarkdownSpec extends ObjectBehavior { ... function it_convers_text_from_an_external_source(Reader $reader) { $reader->getMarkdown()->willReturn("Hi, there"); $this->toHtmlFromReader($reader) ->shouldReturn("<p>Hi, there</p>"); } spec/MarkdownSpec.php exampleに仮引数を書くとPhpSpecが$readerを渡 してくれる
  31. 31. C:¥Users¥ishida¥src¥phpspec>vendor¥bin¥phpspec run --fake ... Would you like me to generate an interface `Reader` for you? [Y/n] ... Would you like me to generate a method signature `Reader::getMarkdown()` for you? [Y/n] ... Do you want me to create `Markdown::toHtmlFromReader()` for you? [Y/n] ... Do you want me to make `Markdown::toHtmlFromReader()` always return '<p>Hi, there</p>' for you? [Y/n] Method Markdown::toHtmlFromReader() has been modified. / skipped: 0% / pending: 0% / passed: 100% / failed: 0% / broken: 0% / 3 examples 1 specs 3 examples (3 passed) 43ms Reader::getMarkdown()と Makkdown::toHtmlFromReader()の仮実装の 自動生成
  32. 32. モック 変換したHTMLをファイル等に出力するメソッド Makdown::outputHtml($text, Writer $writer); 生成したHTMLをWriter::writeText($text)メソッドに出力する 指定したメソッドが呼ばれたかを検査可能なダミーのWriterオブ ジェクトを作成する(モック)
  33. 33. use Writer; class MarkdownSpec extends ObjectBehavior { ... function it_outputs_converted_text(Writer $writer) { // $writer->writeText("<p>Hi, there</p>") // が呼び出されること $this->outputHtml("Hi, there", $writer); } spec/MarkdownSpec.php
  34. 34. use Writer; class MarkdownSpec extends ObjectBehavior { ... function it_outputs_converted_text(Writer $writer) { $writer->writeText("<p>Hi, there</p>") ->shouldBeCalled(); $this->outputHtml("Hi, there", $writer); } spec/MarkdownSpec.php できました
  35. 35. class MarkdownTest extends ¥PHPUnit_Framework_TestCase { function test_output_converted_text() { $writer = $this->getMockBuilder('Writer') ->setMethods(['writeText']) ->getMock(); $writer->expects($this->once()) ->method('writeText') ->with($this->equalTo("<p>Hi, there</p>")); $markdown = new Markdown(); $markdown->outputHtml("Hi, there", $writer); } 但しPHPUnit4.5以降では、Prophecyという phpspecのMockフレームワークを使うこともで きます ちなみにPHPUnitで書くと
  36. 36. コンストラクタ Writerをメソッドの引数ではなくコンストラクタの引数として渡 し $markdown = new Markdown(); $markdown->outputHtml($text, $writer); ではなく $markdown = new Markdown($writer); $markdown->outputHtml($text); にする
  37. 37. use Writer; class MarkdownSpec extends ObjectBehavior { ... function it_outputs_converted_text(Writer $writer) { $writer->writeText("<p>Hi, there</p>") ->shouldBeCalled(); $this->outputHtml("Hi, there", $writer); } spec/MarkdownSpec.php $writerをoutputHtmlの引数ではなく、 コンストラクタで渡したい
  38. 38. use Writer; class MarkdownSpec extends ObjectBehavior { ... function let(Writer $writer) { $this->beConstructedWith($writer); } function it_outputs_converted_text($writer) { $writer->writeText("<p>Hi, there</p>") ->shouldBeCalled(); $this->outputHtml("Hi, there"); } spec/MarkdownSpec.php letは各exampleの前に実行される letとexampleの引数には同じ$writerが渡される (変数名が重要)
  39. 39. Point スタブのメソッドが値を 返すこと $reader->getMarkdown() ->willReturn("Hi, There") モックのメソッドが呼び 出されること $writer->writeText("<p>Hi, there</p>") ->shouldBeCalled() コンストラクタに引数が 渡されること $this->beConstructedWith($writer)
  40. 40. 得られたもの MarkdownクラスのSpec Markdownクラスの実装 Readerインターフェース Writerインターフェース
  41. 41. なぜinterface? class FileStorage implements Reader, Writer { ... } class Markdown { function toHtmlFromReader(Reader $reader) {...} function toHtmlFromReader(FileStorage $reader) {...} } このメソッドに最 低限必要なものを 表せている
  42. 42. 列車事故を防ぐ ((EditSaveCustomer) master.getModelisable() .getDockablePanel() .getCustomizer()) .getSaveItem().setEnabled(Boolean.FALSE.booleanValue()); master.allowSaveingOfCustomisations(); Specで設計を導き 出せばこのように はなりづらい The GOOS Book に載っている例
  43. 43. サイクルを回し続ける スペック→実装により隣接オブジェクトを設計し、さらに隣接オ ブジェクトのスペック→実装を行う が、隣接オブジェクトが既存のもの(ライブラリ、フレームワー ク、Webサービス)の場合はどうする? 突然の システム 境界!!
  44. 44. Hexagonal-Architecture 自分書かない部分のアダプタを 作り、その先は結合テスト等粒 度の大きいテストを行う この時phpspecは使わない (PHPUnit、Behat、Codeception などを使う)
  45. 45. 今日話さなかったこと 今日はUnit Testの話だけしましたが、本来はAcceptance Testあり きです 単体テストをすれば必ず良い設計になるわけではありません。 しかし、単体テストは良い設計にするための手助けをしてくれま す phpspecはどんなテストにも有効なわけではありません。例えば 学習テストのようなケースではPHPUnitなどを使った方が良いで しょう
  46. 46. まとめ London school TDDでは、モックを使って、テスト対象と隣接する オブジェクトとのコミュニケーションをテストする 外から中へアプローチすることで、必要なインターフェースを 導き出す phpspecを使うとモックを簡単に作成することができる モック出来ないものに対してはアダプタを作成する phpspecは、London school TDDの考え方を上手く表現したツール phpspecで書かれたThe GOOS Bookみたいな本が出るとうれしい
  47. 47. 参考資料 モックによるインターフェースの発見 http://d.hatena.ne.jp/digitalsoul/20110927/1317079751 テスト駆動開発の進化 http://d.hatena.ne.jp/digitalsoul/20120920/1348104079 モックとスタブの違い http://d.hatena.ne.jp/devbankh/201002 実践テスト駆動開発(GOOS)読んだ http://qiita.com/kenjihiranabe/items/b951b6d98672167347fd http://iakio.hatenablog.com/archive/category/phpspec http://qiita.com/tags/phpspec
  48. 48. 参考資料 phpspec2: SUS and collaborators(2012) (http://everzet.com/post/33178339051/sus-collaborators) phpspec2のalphaリリース時の作者によるコンセプトの解説 Konstantin Kudryashov - Design How Your Objects Talk Through Mocking at Laracon EU 2014 (https://www.youtube.com/watch?v=X6y-OyMPqfw) Laracon EUでの作者によるモックを使った開発手法に関する講演。 phpspec自体は使われていない Does TDD really lead to good design? (http://codurance.com/2015/05/12/does-tdd-lead-to-good-design/) Detroit schoolとLondon Schoolの比較など Laracast (https://laracasts.com/index/phpspec) LaravelやPHPに関するスクリーンキャスト。一部無料

×