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

テストが辛いを解決するテスト駆動開発のアプローチ at PHPカンファレンス仙台2019

テストが辛いを解決するテスト駆動開発のアプローチ at PHPカンファレンス仙台2019

"テストが辛いを解決するテスト駆動開発のアプローチ" at PHPカンファレンス仙台2019

Avatar for Kazuki Higashiguchi

Kazuki Higashiguchi

January 24, 2019
Tweet

More Decks by Kazuki Higashiguchi

Other Decks in Technology

Transcript

  1. WBS@EVNQ NF  w ౦ޱ࿨ᏻ ,B[VLJ)JHBTIJHVDIJ  w !IHTHUL (JU)VC

    5XJUUFS  w 4FSWFSTJEF&OHJOFFS (P 1)1 FUD  w ॴଐɿ#"4&#"/,גࣜձࣾ w #"4&גࣜձࣾࢠձࣾ
  2. w ςετͷॻ͖ํ͕Θ͔Βͳ͍ w ৗʹҰࠁҰඵΛ૪͍ͬͯΔ w طଘίʔυʹςετ͕ͳ͍ w ςετΛॻ͘ͷ͕ԯ߷ w ςετΛॻ͘ͷ͕೉͍͠ίʔ

    υʹͳΔ w طଘςετ͕։ൃΛ๦͍͛ͯ Δ w ςετͷॻ͖ํͷशಘ d$BVTFd d1PTTJCMZ4PMVUJPOd w ϓϩδΣΫτ؅ཧ w ϨΨγʔίʔυվળ w ςετͷॻ͖΍͍͢ઃܭ w Մಡੑɾϝϯςφϯεੑ ͷߴ͍Ϣχοτςετ w ࣮૷தͷςετͷҐஔͮ ͚ͷݟ௚͠
  3. w ςετͷॻ͖ํ͕Θ͔Βͳ͍ w ৗʹҰࠁҰඵΛ૪͍ͬͯΔ w طଘίʔυʹςετ͕ͳ͍ w ςετΛॻ͘ͷ͕ԯ߷ w ςετΛॻ͘ͷ͕೉͍͠ίʔ

    υʹͳΔ w طଘςετ͕։ൃΛ๦͍͛ͯ Δ d$BVTFd d1PTTJCMZ4PMVUJPOd w ςετͷॻ͖ํͷशಘ w ϓϩδΣΫτ؅ཧ w ϨΨγʔίʔυվળ w ςετͷॻ͖΍͍͢ઃܭ w Մಡੑɾϝϯςφϯεੑ ͷߴ͍Ϣχοτςετ w ࣮૷தͷςετͷҐஔͮ ͚ͷݟ௚͠ Focus points
  4. ςετۦಈ։ൃͷαΠΫϧ w ·ͣ͸ςετΛҰͭॻ͘ w ͢΂ͯͷςετΛ૸Βͤɺ৽͍͠ςετͷࣦഊ Λ֬ೝ͢Δ 3FE  w খ͞ͳมߋΛߦ͏

    w ͢΂ͯͷςετΛ૸Βͤɺ͢΂ͯ੒ޭ͢Δ͜ͱ Λ֬ೝ͢Δ (SFFO  w ϦϑΝΫλϦϯάΛߦͬͯॏෳΛআڈ͢Δ 3FGBDUPS
  5. ςετΛॻ͘ /** * @test */ public function هࣄৄࡉऔಘʹͯ੒ޭϨεϙϯε͕ฦ٫͞ΕΔ() { $this->configRequest([

    'headers' => [ 'Accept' => 'application/json', ], ]); $this->get('/api/articles/view/first'); $this->assertResponseSuccess(); } DEMO ʢࢿྉެ։༻ʣ
  6. খ͞ͳมߋΛߦ͏ class ArticlesController extends AppController { public function initialize() {

    parent::initialize(); $this->loadComponent('RequestHandler'); } public function view($slug = null) { $this->set([ '_serialize' => ['article'], 'article' => [], ]); } } DEMO ʢࢿྉެ։༻ʣ
  7. ςετΛॻ͘ /** * @test */ public function هࣄৄࡉऔಘʹͯଘࡏ͢ΔϨίʔυͷ৔߹ɺهࣄ৘ใ͕ฦ٫͞ΕΔ() { $this->configRequest([

    'headers' => [ 'Accept' => 'application/json', ], ]); $this->get('/api/articles/view/first'); $expected = [ 'article' => [ 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'slug' => 'first', 'body' => 'First Article Body', 'published' => 1, 'created' => '2018-01-07T15:47:01+00:00', 'modified' => '2018-01-07T15:47:02+00:00', ], ]; $expected = json_encode($expected, JSON_PRETTY_PRINT); $this->assertSame($expected, (string)$this->_response->getBody()); } DEMO ʢࢿྉެ։༻ʣ
  8. Ծ࣮૷ public function view($slug = null) { // TODO temporary

    implement $article = [ 'id' => 1, 'user_id' => 1, 'title' => 'First Article', 'slug' => 'first', 'body' => 'First Article Body', 'published' => 1, 'created' => '2018-01-07T15:47:01+00:00', 'modified' => '2018-01-07T15:47:02+00:00', ]; $this->set([ '_serialize' => ['article'], 'article' => $article, ]); } DEMO ʢࢿྉެ։༻ʣ
  9. ϦϑΝΫλϦϯά public function view($slug = null) { $article = $this->Articles->findBySlug($slug)-

    >firstOrFail(); $this->set([ '_serialize' => ['article'], 'article' => $article, ]); } DEMO ʢࢿྉެ։༻ʣ
  10. w ςετͷॻ͖ํ͕Θ͔Βͳ͍ w ৗʹҰࠁҰඵΛ૪͍ͬͯΔ w طଘίʔυʹςετ͕ͳ͍ w ςετΛॻ͘ͷ͕ԯ߷ w ςετΛॻ͘ͷ͕೉͍͠ίʔ

    υʹͳΔ w طଘςετ͕։ൃΛ๦͍͛ͯ Δ d$BVTFd d1PTTJCMZ4PMVUJPOd w ςετͷॻ͖ํͷशಘ w ϓϩδΣΫτ؅ཧ w ϨΨγʔίʔυվળ w ςετͷॻ͖΍͍͢ઃܭ w Մಡੑɾϝϯςφϯεੑ ͷߴ͍Ϣχοτςετ w ࣮૷தͷςετͷҐஔͮ ͚ͷݟ௚͠ Focus points ৼΓฦΓ
  11. ݱ࣌఺ͷςετ /** * @test */ public function ໰͍߹ΘͤϖʔδʹΞΫηεͰ͖Δ() { $this->get('/inquiries/add');

    $this->assertResponseOk(); } /** * @test */ public function ໰͍߹Θͤอଘ੒ޭ࣌Ұཡϖʔδ΁ϦμΠϨΫτ͢Δ() { $this->enableCsrfToken(); $this->enableSecurityToken(); $data = [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ]; $this->post('/inquiries/add', $data); $this->assertResponseSuccess(); $this->assertRedirect('/inquiries'); }
  12. ݱ࣌఺ͷ࣮૷ public function add() { $inquiry = $this->Inquiries->newEntity(); if ($this->request->is('post'))

    { // TODO save inquiry data return $this->redirect(['action' => 'index']); } $this->set(compact('inquiry')); }
  13. ·ͣ͸ςετΛॻ͘ /** * @test */ public function ໰͍߹Θͤͨ͠಺༰͕inquiriesςʔϒϧʹอଘ͞ΕΔ() { $this->enableCsrfToken();

    $this->enableSecurityToken(); $this->configRequest([ 'environment' => ['REMOTE_ADDR' => '12.34.56.78'], ]); $data = [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ]; $this->post('/inquiries/add', $data); /** @var Inquiry $saved_inquiry */ $saved_inquiry = TableRegistry::getTableLocator() ->get('inquiries')->find() ->where(['title' => 'sample'])->first(); $this->assertInstanceOf('\App\Model\Entity\Inquiry', $saved_inquiry); }
  14. ςετΛ௨ͨ͢ΊͷԾ࣮૷ public function add() { $inquiry = $this->Inquiries->newEntity(); if ($this->request->is('post'))

    { // FIXME temporary $inquiry->title = 'sample'; $inquiry->body = 'sample'; $inquiry->email = '[email protected]'; $inquiry->client_ip = ’12.34.56.78'; $this->Inquiries->save($inquiry); return $this->redirect(['action' => 'index']); } $this->set(compact('inquiry')); }
  15. ࣮૷ऀ͕಄ʹු͔Μ࣮ͩ૷ public function saveWithIp(Inquiry $entity, \Cake\Http\ServerRequest $request) { $entity->client_ip =

    $request->clientIp(); return $this->save($entity); } w .PEFM૚ʹϝιουΛ࡞੒ w ϦΫΤετΦϒδΣΫτΛड͚औͬͯอଘ
  16. ςετΛॻ͘ /** * @test */ public function ໰͍߹Θͤͨ͠಺༰͕inquiriesςʔϒϧʹอଘ͞ΕΔ() { $inquiry

    = $this->Inquiries->newEntity( [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ] ); $request = new \Cake\Http\ServerRequest('/inquiries/ add'); $actual = $this->Inquiries->saveWithIp($inquiry, $request); // Assert something } w ϝιου࡞੒Λ૝ఆ͍ͯ͠Δ.PEFMʹςετΛॻ͘
  17. ςετ͔Βҧ࿨ײΛټΈऔΔ /** * @test */ public function ໰͍߹Θͤͨ͠಺༰͕inquiriesςʔϒϧʹอଘ͞ΕΔ() { $inquiry

    = $this->Inquiries->newEntity( [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ] ); $request = new \Cake\Http\ServerRequest('/inquiries/ add'); $actual = $this->Inquiries->saveWithIp($inquiry, $request); // Assert something } ϢχοτςετΛॻͨ͘Ίʹɺ ςετ༻ͷϦΫΤετΛੜ੒͢Δख͕ؒൃੜ ͍ͯ͠Δ
  18. ҧ࿨ײ͔Βઃܭ൑அ͢Δ /** * @test */ public function ໰͍߹Θͤͨ͠಺༰͕inquiriesςʔϒϧʹอଘ͞ΕΔ() { $inquiry

    = $this->Inquiries->newEntity( [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ] ); $request = new \Cake\Http\ServerRequest('/inquiries/ add'); $actual = $this->Inquiries->saveWithIp($inquiry, $request); // Assert something } ϞσϧΫϥε͕ɺ ϦΫΤετΫϥεʹґଘ͍ͯ͠Δઃܭ͸ɺ ΍Ίͨ΄͏͕ྑͦ͞͏ͩ
  19. ςετΛม͑ͯΈΔ /** * @test */ public function ໰͍߹Θͤͨ͠಺༰͕inquiriesςʔϒϧʹอଘ͞ΕΔ() { $inquiry

    = $this->Inquiries->newEntity( [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ‘client_ip' => ‘171.0.0.1’, ] ); $actual = $this->Inquiries->saveWithIp($inquiry); // Assert something } ϦΫΤετΫϥε΁ͷґଘΛɺ ςετίʔυͷஈ֊Ͱഉআͨ͠ɻ
  20. ςετΛม͑ͯΈΔ /** * @test */ public function ໰͍߹Θͤͨ͠಺༰͕inquiriesςʔϒϧʹอଘ͞ΕΔ() { $inquiry

    = $this->Inquiries->newEntity( [ 'title' => 'sample', 'body' => 'sample', 'email' => '[email protected]', ‘client_ip' => ‘171.0.0.1’, ] ); $actual = $this->Inquiries->saveWithIp($inquiry); // Assert something } ݁ՌతʹɺΤϯςΟςΟΛอଘ͢Δ͚ͩʹ ͳͬͨɻ ϝιουΛ࡞੒͢Δ͜ͱ͕ͦ΋ͦ΋ෆཁ ͱ൑அɻ
  21. Ծ࣮૷Λॻ͖׵͑Δ public function add() { $inquiry = $this->Inquiries->newEntity(); if ($this->request->is('post'))

    { $inquiry = $this->Inquiries->patchEntity($inquiry, $this- >request->getData()); $inquiry->client_ip = $this->request->clientIp(); if (!$this->Inquiries->save($inquiry)) { // TODO error handling } return $this->redirect(['action' => 'index']); } $this->set(compact('inquiry')); }
  22. ࣦഊ͢ΔςετΛॻ͘ /** * @test */ public function ΞΧ΢ϯτ৘ใ͕σʔλϕʔεʹอଘ͞ΕΔ() { $result

    = $this->AccountService->register(); // σʔλϕʔεʹอଘ͞Εͨ஋ΛΞαʔτ͢Δ }
  23. ͞ΒʹςετΛ௥Ճ͢Δ /** * @test */ public function ΞΧ΢ϯτ৘ใ͕σʔλϕʔεʹอଘ͞ΕΔ() { $result

    = $this->AccountService->register(); // σʔλϕʔεʹอଘ͞Εͨ஋ΛΞαʔτ͢Δ } /** * @test */ public function ొ࿥׬ྃϝʔϧΛૹ৴͞ΕΔ() { $result = $this->AccountService->register(); // ొ࿥׬ྃϝʔϧ͕ૹ৴͞Εͨ͜ͱΛΞαʔτ͢Δ }
  24. ࣦഊ͢ΔςετΛॻ͘ /** * @test */ public function ొ࿥׬ྃϝʔϧΛૹ৴͞ΕΔ() { $result

    = $this->AccountMailer->send(); // ొ࿥׬ྃϝʔϧ͕ૹ৴͞Εͨ͜ͱΛΞαʔτ͢Δ } w ϝʔϧૹ৴Λߦ͏"DDPVOU.BJMFSΫϥεͷςε τΛ࡞੒ɺ"DDPVOU4FSWJDF5FTU͔Βςετϝιο υΛҠಈ͢Δ
  25. ςετ͕ͳ͍ίʔυ public function index() { $articles = $this->Articles->find('all'); $this->set([ '_serialize'

    => ['articles'], 'articles' => $articles, ]); } w هࣄҰཡΛKTPOͰฦ٫͢Δγϯϓϧͳ"1* w lެ։͍ͯ͠Δ QVCMJTIFE zهࣄͷΈฦ٫͢ΔมߋΛ૝ఆ w ͔͠͠ɺલ೚ऀʹΑΔςετίʔυ͸ॻ͔Ε͍ͯͳ͔ͬͨ
  26. ର৅ίʔυΛݺͼग़͢ /** * @test */ public function هࣄҰཡऔಘʹͯهࣄҰཡ͕ฦ٫͞ΕΔ() { $this->configRequest([

    'headers' => [ 'Accept' => 'application/json', ], ]); $this->get('/api/articles/index'); }
  27. ࣦഊ͢Δද໌Λߦ͏ /** * @test */ public function هࣄҰཡऔಘʹͯهࣄҰཡ͕ฦ٫͞ΕΔ() { $this->configRequest([

    'headers' => [ 'Accept' => 'application/json', ], ]); $this->get('/api/articles/index'); $expected = json_encode([], JSON_PRETTY_PRINT); $this->assertSame($expected, (string)$this- >_response->getBody()); }
  28. ςετΛมߋ͢Δ $expected = [ 'articles' => [ [ 'id' =>

    1, 'user_id' => 1, 'title' => 'First Published Article', 'slug' => 'first-published-article', 'body' => 'First Published Body', 'published' => 1, 'created' => '2018-01-07T15:47:01+00:00', 'modified' => '2018-01-07T15:47:02+00:00', ], [ 'id' => 2, 'user_id' => 1, 'title' => 'Unpublished Article', 'slug' => 'unpublished-article', 'body' => 'Unpublished Article Body', 'published' => 0, 'created' => '2018-01-07T15:47:01+00:00', 'modified' => '2018-01-07T15:47:02+00:00', ], ], ]; $expected = json_encode($expected, JSON_PRETTY_PRINT); $this->assertSame($expected, (string)$this->_response->getBody());
  29. มߋ͍ͨ͠ৼΔ෣͍Λςετʹॻ͘ $expected = [ 'articles' => [ [ 'id' =>

    1, 'user_id' => 1, 'title' => 'First Published Article', 'slug' => 'first-published-article', 'body' => 'First Published Body', 'published' => 1, 'created' => '2018-01-07T15:47:01+00:00', 'modified' => '2018-01-07T15:47:02+00:00', ], ], ]; $expected = json_encode($expected, JSON_PRETTY_PRINT); $this->assertSame($expected, (string)$this->_response->getBody()); w ެ։த QVCMJTIFE ͷΈฦ٫͞ΕΔৼΔ෣͍ Λظ଴஋ͱͯ͠ઃఆ͢Δ
  30. ςετΛ௨࣮͢૷Λߦ͏ public function index() { $articles = $this->Articles ->find() ->where(['published'

    => 1]) ->all(); $this->set([ '_serialize' => ['articles'], 'articles' => $articles, ]); }