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

PHPバージョンアップと決済リプレイスを支えたユニットテスト #phpcon

PHPバージョンアップと決済リプレイスを支えたユニットテスト #phpcon

PHP Conference 2018での登壇資料です。

- なぜ我々はユニットテストが必要なのか
- 既存コードを保護する技法
- ユニットテスト量を増やす試み
- ユニットテスト増加における課題と対策
- テストがないコードを再生産しない技法
- まとめ

Kazuki Higashiguchi

December 13, 2018
Tweet

More Decks by Kazuki Higashiguchi

Other Decks in Technology

Transcript

  1. About me • ౦ޱ ࿨ᏻ (@higasgt) • Server Side EngineerʢPHP

    / Goʣ • BASE, Inc. / BASE Product Division • PHPόʔδϣϯΞοϓ / ޙ෷͍ܾࡁϦϓϨΠε / YELL BANK • Blog: http://khigashigashi.hatenablog.com/
  2. - طଘίʔυΛอޢ͢Δٕ๏ - 1. ࢓༷Խςετ • “characterization test” • ίʔυͷطଘͷৼΔ෣͍Λ໌Β͔ʹ͢Δς

    ετ • ॲཧ಺Ͱૢ࡞͢Δཁૉʹର͢Δ࣮ࢪ͍ͯ͠ ͘ • Viewʹηοτ͞ΕΔ஋ • Sessionͷ஋ • Dataͷొ࿥/ߋ৽ • FUD 
  3. • “exploratory refactoring” • طଘίʔυʹର͢ΔཧղΛਂΊΔ • “ʮະ஌΁ͷڪΕʯΛࠀ෰͢Δ࠷ྑͷํ๏͸ɺͦͷ ίʔυʹඈͼࠐΜͰɺ͍͡Γ࢝ΊΔ͜ͱͩɻ” (ʰϨΨ γʔιϑτ΢ΣΞվળΨΠυʱ)

    • ཧղΛਂΊΔͨΊิॿͱͯ͠ʮ࢓༷Խςε τʯΛߦ͏  https://www.shoeisha.co.jp/book/detail/9784798145143 - طଘίʔυΛอޢ͢Δٕ๏ɿ1. ࢓༷Խςετ - ௐࠪతϦϑΝΫλϦϯά
  4. 1. ςετίʔυͷதͰର৅ͷίʔυΛݺͼ ग़͢ 2. ࣦഊ͢ΔͱΘ͔͍ͬͯΔද໌Λॻ͘ 3. ࣦഊͨ݁͠Ռ͔Β࣮ࡍͷৼΔ෣͍Λ֬ೝ ͢Δ 4. ίʔυ͕࣮ݱ͢ΔৼΔ෣͍Λظ଴͢ΔΑ

    ͏ʹɺςετΛมߋ͢Δ 5. Ҏ্ͷखॱΛ܁Γฦ͢  - طଘίʔυΛอޢ͢Δٕ๏ɿ1. ࢓༷Խςετ - ࢓༷Խςετͷखॱ
  5. - 1. ࢓༷Խςετɿ࢓༷Խςετͷखॱ - 1. ςετίʔυͷதͰର৅ͷίʔυΛ ݺͼग़͢ $result = $this->testAction(

    ‘/samples/index’, [‘data’ => $data, ‘method’ => ‘post’] ); 1. ςετίʔυͷதͰର৅ͷίʔυΛݺͼग़͢
  6. $result = $this->testAction( ‘/samples/index’, [‘data’ => $data, ‘method’ => ‘post’]

    ); $expected = []; $this->assertSame($expected, $this->vars); 2. ࣦഊ͢ΔͱΘ͔͍ͬͯΔද໌Λॻ͘ - 1. ࢓༷Խςετɿ࢓༷Խςετͷखॱ - 2. ࣦഊ͢ΔͱΘ͔͍ͬͯΔද໌Λॻ͘
  7. $ ./Console/cake test app —-stderr Controller/SampleController PHPUnit 5.7.27 by Sebastian

    Bergmann and contributors. F 1 / 1 (100%) Time: 19.32 seconds, Memory: 16.00MB There was 1 failure: --- Expected +++ Actual @@ @@ -Array &0 () +Array &0 ( ‘result’ => [‘success’] ) 3. ࣦഊͨ݁͠Ռ͔Β࣮ࡍͷৼΔ෣͍Λ֬ೝ͢Δ - 1. ࢓༷Խςετɿ࢓༷Խςετͷखॱ - 3. ࣦഊͨ݁͠Ռ͔Β࣮ࡍͷৼΔ෣͍Λ ֬ೝ͢Δ
  8. $result = $this->testAction( ‘/samples/index’, [‘data’ => $data, ‘method’ => ‘post’]

    ); $expected = [ ‘result’ => [‘success’] ]; $this->assertSame($expected, $this->vars); 4. ίʔυ͕࣮ݱ͢ΔৼΔ෣͍Λظ଴͢ΔΑ͏ ʹɺςετΛมߋ͢Δ 5. Ҏ্ͷखॱΛ܁Γฦ͢ - 1. ࢓༷Խςετɿ࢓༷Խςετͷखॱ - 4. ίʔυ͕࣮ݱ͢ΔৼΔ෣͍Λظ଴͢ ΔΑ͏ʹɺςετΛมߋ͢Δ
  9. 1. ࢓༷ԽςετΛॻ͘ 2. ςετ͕௨Δ͜ͱΛ֬ೝ͢Δ 3. ϦϑΝΫλϦϯά͢Δ 4. ςετ͕௨Δ͜ͱΛ֬ೝ͢Δ  -

    طଘίʔυΛอޢ͢Δٕ๏ɿ1. ࢓༷Խςετ - ҆શͳϦϑΝΫλϦϯάͷखॱ
  10. - ҆શͳϦϑΝΫλϦϯάɿྫ - ର৅ϝιου /** * ར༻Մೳ͔Ͳ͏͔Λ൑ఆ͢Δ * @param int|null

    $user_id * @return bool */ public function canUse(int $user_id = null): bool { if (!is_null($user_id)) { $user = $this->User->find('first', [ 'conditions' => ['user_id' => $user_id] ] ); if (empty($user)) { return false; } $available = $user['User']['available']; return $available; } else { return false; } } ૣظreturn͢ΔίʔυʹϦϑΝΫλϦϯά͍ͨ͠৔߹
  11. /** * @dataProvider dataProvider_canUse * @param int|null $user_id * @param

    bool $expected */ public function test_canUse(?int $user_id, bool $expected): void { $this->assertSame($expected, $this->User->canUse($user_id)); } public function dataProvider_canUse(): array { return [ 'user_id͕null' => [null, false], 'ଘࡏ͢Δuser_idͰར༻Մೳ' => [1, true], 'ଘࡏ͢Δuser_idͰར༻ෆՄ' => [2, false], ]; } ϦϑΝΫλϦϯάର৅ϝιουʹର͢ΔςετΛॻ͘ - ҆શͳϦϑΝΫλϦϯάɿྫ - 1. ࢓༷ԽςετΛॻ͘
  12. /** * @dataProvider dataProvider_canUse * @param int|null $user_id * @param

    bool $expected */ public function test_canUse(?int $user_id, bool $expected): void { $this->assertSame($expected, $this->User->canUse($user_id)); } public function dataProvider_canUse(): array { return [ 'user_id͕null' => [null, false], 'ଘࡏ͢Δuser_idͰར༻Մೳ' => [1, true], 'ଘࡏ͢Δuser_idͰར༻ෆՄ' => [2, false], ]; } PHPUnitͷdataProviderΛར༻͢Δ͜ͱͰςετέʔεΛγϯϓϧʹ - ҆શͳϦϑΝΫλϦϯάɿྫ - 1. ࢓༷ԽςετΛॻ͘
  13. -> % ./app/Console/cake test app User --filter test_canUse PHPUnit 5.7.27

    by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 5.9 seconds, Memory: 14.00MB OK (3 tests, 3 assertions) - ҆શͳϦϑΝΫλϦϯάɿྫ - 2. ςετ͕௨Δ͜ͱΛ֬ೝ͢Δ
  14. - ҆શͳϦϑΝΫλϦϯάɿྫ - 3. ϦϑΝΫλϦϯά͢Δ /** * ར༻Մೳ͔Ͳ͏͔Λ൑ఆ͢Δ * @param

    int|null $user_id * @return bool */ public function canUse(int $user_id = null): bool { if (is_null($user_id)) { return false; } $user = $this->User->find('first', [ 'conditions' => ['user_id' => $user_id] ] ); if (empty($user)) { return false; } $available = $user['User']['available']; return $available; } ૣظreturn͢ΔίʔυʹϦϑΝΫλϦϯά
  15. -> % ./app/Console/cake test app User --filter test_canUse PHPUnit 5.7.27

    by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 5.9 seconds, Memory: 14.00MB OK (3 tests, 3 assertions) - ҆શͳϦϑΝΫλϦϯάɿྫ - 4. ςετ͕௨Δ͜ͱΛ֬ೝ͢Δ
  16. • ςετ͕͔͚ΔΑ͏ʹॻ͖׵͑Δ • ີ݁߹ΛMockΛࠩ͠ࠐΊΔஈ֊·Ͱղফ ͢Δ • ςετ࣌ʹMockΛࠩ͠ࠐΉ • ֎෦࿈ܞͷৼΔ෣͍ͷMockΫϥεΛ࡞੒ •

    ֎෦࿈ܞͳͲΛؚΉςετΛߦ͏ࡍɺ֘ ౰ΫϥεΛMockʹஔ͖׵͑Δ  - طଘίʔυΛอޢ͢Δٕ๏ɿ2. ϨΨγʔίʔυ - ςετ͠ʹ͍͘ίʔυ΁ͷରࡦ
  17. • σβΠϯύλʔϯͷҰͭ • ֎෦͔Βґଘؔ܎ͷ͋ΔΦϒδΣΫτΛ஫ ೖ͢Δ • ;Δ·͍ͱґଘੑղܾΛ෼཭ • https://github.com/google/guice/wiki/Motivation •

    Martin FowlerࢯʹΑΓఏএ͞Εͨ༻ޠ • https://www.martinfowler.com/articles/injection.html  - طଘίʔυΛอޢ͢Δٕ๏ɿ2. ϨΨγʔίʔυ - Dependency Injection
  18. େ͖͘3ͭͷDIͷ΍Γํ • Constructor Injection • Setter Injection • Interface Injection

     - Dependency Injection - DI Pattern IUUQTXXXNBSUJOGPXMFSDPNBSUJDMFTJOKFDUJPOIUNM
  19. // Before public function charge($order) { $manager = new PaymentManager();

    $result = $manager->charge($order); // ܾࡁγεςϜʹ௨஌ } PaymentManagerͱີ݁߹ɺ Mock΁ͷࠩ͠ସ͕͑೉͍͠ - ςετ͠ʹ͍͘ίʔυɿྫ - खಈDIͰີ݁߹Λղফ͢Δ
  20. // After public function __construct($PaymentManager = null) { $this->manager =

    $PaymentManager ?? new PaymentManager(); } public function charge($order) { $result = $this->manager->charge($order); // ܾࡁγεςϜʹ௨஌ } Constructor Injection ίϯετϥΫλͷҾ਺Ͱ౉͢͜ͱͰɺ Mock΁ͷࠩ͠ସ͑Λ༰қʹ͢Δ - ςετ͠ʹ͍͘ίʔυɿྫ - खಈDIͰີ݁߹Λղফ͢Δ
  21. // mock objectΛ࡞Δ $mock = $this->getMockBuilder(Sample::class) ->setMethods(['update']) ->getMock(); // mock

    objectͷϝιουͷظ଴஋ͱฦ͢஋Λఆٛ $mock->expects($this->once()) ->method('update') ->with($this->equalTo(‘param1')) ->will($this->returnValue(true); // Constructor injection͢Δ৔߹ $target = new Target($mock); IUUQTQIQVOJUEFNBOVBMFOUFTUEPVCMFTIUNM - ςετ͠ʹ͍͘ίʔυɿྫ - खಈDIͰີ݁߹Λղফ͢Δ
  22. // mock objectΛ࡞Δ $mock = $this->getMockBuilder(Sample::class) ->setMethods(['update']) ->getMock(); // mock

    objectͷϝιουͷظ଴஋ͱฦ͢஋Λఆٛ $mock->expects($this->once()) ->method('update') ->with($this->equalTo(‘param1')) ->will($this->returnValue(true); // Constructor injection͢Δ৔߹ $target = new Target($mock);  MockΦϒδΣΫτΛ࡞੒͢Δ (PHPUnit Test Doubles) https://phpunit.de/manual/6.5/en/test-doubles.html - ςετ͠ʹ͍͘ίʔυɿྫ - खಈDIͰີ݁߹Λղফ͢Δ
  23. // mock objectΛ࡞Δ $mock = $this->getMockBuilder(Sample::class) ->setMethods(['update']) ->getMock(); // mock

    objectͷϝιουͷظ଴஋ͱฦ͢஋Λఆٛ $mock->expects($this->once()) ->method('update') ->with($this->equalTo(‘param1')) ->will($this->returnValue(true); // Constructor injection͢Δ৔߹ $target = new Target($mock);  - ςετ͠ʹ͍͘ίʔυɿྫ - खಈDIͰີ݁߹Λղফ͢Δ Constructor InjectionΛߦ͏
  24. // ର৅ؔ਺ఆٛΛॻ͖׵͑Δ $val = <<<_XML__ <response>success</response> __XML__; runkit_function_copy(‘curl_exec’, ‘curl_exec_org’); runkit_function_redefine(‘curl_exec’,

    ‘’, $val); // ςετର৅ίʔυΛ࣮ߦ $result = $this->Sample->exec(); // ॻ͖׵͑ͨؔ਺ఆٛΛ΋ͱʹ໭͢ runkit_function_remove(‘curl_exec’); runkit_function_copy(‘curl_exec_org’, ‘curl_exec’); runkit_function_remove(‘curl_exec_org’); ؔ਺ఆٛͷόοΫΞοϓΛऔΓɺظ଴͢ΔϨεϙ ϯε($val)Λฦ͢Α͏ʹ࠶ఆٛ͢Δ - ςετ͠ʹ͍͘ίʔυͷน - runkitΛ༻͍ͨςετ
  25. // ର৅ؔ਺ఆٛΛॻ͖׵͑Δ $val = <<<_XML__ <response>success</response> __XML__; runkit_function_copy(‘curl_exec’, ‘curl_exec_org’); runkit_function_redefine(‘curl_exec’,

    ‘’, $val); // ςετର৅ίʔυΛ࣮ߦ $result = $this->Sample->exec(); // ॻ͖׵͑ͨؔ਺ఆٛΛ΋ͱʹ໭͢ runkit_function_remove(‘curl_exec’); runkit_function_copy(‘curl_exec_org’, ‘curl_exec’); runkit_function_remove(‘curl_exec_org’); ςετର৅ίʔυΛ࣮ߦ - ςετ͠ʹ͍͘ίʔυͷน - runkitΛ༻͍ͨςετ
  26. // ର৅ؔ਺ఆٛΛॻ͖׵͑Δ $val = <<<_XML__ <response>success</response> __XML__; runkit_function_copy(‘curl_exec’, ‘curl_exec_org’); runkit_function_redefine(‘curl_exec’,

    ‘’, $val); // ςετର৅ίʔυΛ࣮ߦ $result = $this->Sample->exec(); // ॻ͖׵͑ͨؔ਺ఆٛΛ΋ͱʹ໭͢ runkit_function_remove(‘curl_exec’); runkit_function_copy(‘curl_exec_org’, ‘curl_exec’); runkit_function_remove(‘curl_exec_org’); όοΫΞοϓΛऔ͍ͬͯͨ΋ͱ΋ͱͷؔ਺ఆٛʹ ࠶ఆٛ͠௚͢ - ςετ͠ʹ͍͘ίʔυͷน - runkitΛ༻͍ͨςετ
  27. - ഁ୼͠΍͍͢ςετྫ - ֎෦ཁҼͷมԽʹӨڹ͞ΕΔςετ // test༻CloudSearchʹ઀ଓ͢Δ $search = CloudSearchWrapper::factory(); $search->conditions()

    ->keyword(‘test’, ‘title’) ->sort(‘list_order asc’); // test༻CloudSearchʹσʔλొ࿥ $search->documentBatch($this->addJson()); // σʔλొ࿥͞ΕΔ·Ͱ͙͢ʹݕࡧͰ͖ͳ͍ͷͰɺ଴ͭ sleep(3); // ݕࡧ࣮ߦ $result = $search->search($search->conditions()->query());  ωοτϫʔΫঢ়گʹґଘͯ͠མͪΔ
  28. $data = [‘name’ => ‘hogehoge’]; $result = $this->Sample->saveSample($data); $expected =

    [ ‘id’ => 1, ‘name’ => ‘hogehoge’, ]; $data = ClassRegistry::init(‘Sample’)->find(‘first’, [ ‘conditions’ => [‘name’ => ‘hogehoge’] ]; $this->assertSame($expected, $data);  Sample modelʹରԠ͢ΔϑΟΫενϟʹɺ ϨίʔυΛೖΕΔͱɺ ɹsave࣌ͷid͕auto increment͞ΕམͪΔ - ഁ୼͠΍͍͢ςετྫ - FixtureͷมԽʹහײʹམͪΔςετ
  29. - ςετεΠʔτ֦ுʹΑΔԉॿɿྫ - CakePHP2Ͱͷ֦ுྫʢ1ʣ class UserTest extends AppTestCase { public

    $fixtures = [ 'app.user', ]; public $autoFixtures = false; public function test_canUser_ར༻ՄೳϢʔβʔ(): void { $this->loadFixturesWithRecords([ 'User' => [ UserTestHelper::availableUser, ], ]); $this->assertTrue($this->User->canUse(1)); } } 
  30. - ςετεΠʔτ֦ுʹΑΔԉॿɿྫ - CakePHP2Ͱͷ֦ுྫʢ1ʣ class UserTest extends AppTestCase { public

    $fixtures = [ 'app.user', ]; public $autoFixtures = false; public function test_canUser_ར༻ՄೳϢʔβʔ(): void { $this->loadFixturesWithRecords([ 'User' => [ UserTestHelper::availableUser, ], ]); $this->assertTrue($this->User->canUse(1)); } }  CakePHPͷCakeTestCaseΛ֦ுɻ ඞཁͳςʔϒϧɾϨίʔυΛςετέʔε಺Ͱload͢Δ
  31. - ςετεΠʔτ֦ுʹΑΔԉॿɿྫ - CakePHP2Ͱͷ֦ுྫʢ1ʣ <?php class UserTestHelper { const availableUser

    = [ 'id' => 1, 'name' => 'hogehuga', 'available' => 1, 'created' => '2018-12-15 15:30:00', 'modified' => '2018-12-15 15:30:00', ]; }  ςετέʔε͝ͱʹඞཁͳϨίʔυঢ়ଶΛఆٛ
  32. - ςετεΠʔτ֦ுʹΑΔԉॿɿྫ - CakePHP2Ͱͷ֦ுྫʢ2ʣ class UserTest extends AppTestCase { public

    $fixtures = [ 'app.user', ]; public $autoFixtures = false; public function test_canUser_ར༻ՄೳϢʔβʔ(): void { $this->loadFixturesWithRecords([ 'User' => ‘User/User.php’, ]); $this->assertTrue($this->User->canUse(1)); } }  ϑΝΠϧͰςετέʔεʹඞཁͳϨίʔυΛࢦఆ͢Δ
  33. - ςετεΠʔτ֦ுʹΑΔԉॿɿྫ - CakePHP2Ͱͷ֦ுྫʢ2ʣ <?php return [ [ 'id' =>

    1, 'available' => 1, ] ];  ςετέʔεʹͱͬͯ໌ࣔతʹඞཁͳΧϥϜͷΈ஋Λࢦఆ͢Δɻ ࢦఆ͍ͯ͠ͳ͍ΧϥϜ͸ɺςετ࣮ߦ࣌ʹfzaninotto/Fakerʹ ΑΓμϛʔͷ஋Λࣗಈઃఆ IUUQTHJUIVCDPNG[BOJOPUUP'BLFS
  34. • “Test-Driven Development: TDD” • ࣗಈԽ͞ΕͨςετʹΑͬͯ։ൃΛਪ͠ਐΊ Δ • Kent Beck

    ஶʰςετۦಈ։ൃʱ • https://shop.ohmsha.co.jp/shopdetail/000000004967/  - ςετ͕ͳ͍ίʔυΛ࠶ੜ࢈͠ͳ͍ٕ๏ - 2. ςετۦಈ։ൃ
  35. 1. ·ͣ͸ςετΛ̍ͭॻ͘ 2. ͢΂ͯͷςετΛ૸Βͤɺ৽͍͠ςετͷ ࣦഊΛ֬ೝ͢Δ 3. খ͞ͳมߋΛߦ͏ 4. ͢΂ͯͷςετΛ૸Βͤɺશͯ੒ޭ͢Δ͜ ͱΛ֬ೝ͢Δ

    5. ϦϑΝΫλϦϯάΛߦͬͯॏෳΛআڈ͢Δ  - ςετۦಈ։ൃ - ςετۦಈ։ൃͷϦζϜ IUUQTTIPQPINTIBDPKQTIPQEFUBJM