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

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

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

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

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' => 'hgsgtk@gmail.com', ]; $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' => 'hgsgtk@gmail.com', ]; $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 = 'hgsgtk@gmail.com'; $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' => 'example@example.com', ] ); $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' => 'example@example.com', ] ); $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' => 'example@example.com', ] ); $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' => 'example@example.com', ‘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' => 'example@example.com', ‘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, ]); }