「質」のいいユニットテストを書くためのプラクティス / practices to write better unit test

「質」のいいユニットテストを書くためのプラクティス / practices to write better unit test

#phperkaigi 2019での発表資料です。

88964b936e864ca7d326272eaa70fa9a?s=128

Kazuki Higashiguchi

March 29, 2019
Tweet

Transcript

  1. 6.

    © - BASE, Inc. ⾃⼰紹介 東⼝和暉 (Kazuki Higashiguchi) Twitter /

    GitHub : @hgsgtk サーバーサイドエンジニア BASE BANK, Inc. / Dev Division
  2. 16.

    © - BASE, Inc. コスト削減の指標 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  3. 17.

    © - BASE, Inc. 「テストの経済性」 • 『xUnit Test Patterns』より 「テストの経済性」

    • 最初は、⾃動化テストの学習‧ 実践コストによってコストが嵩 む • 徐々に落ち着いてきて、テスト による節約コストも増えてくる • 結果として、コストは相殺され ていく ref: https://qiita.com/hgsgtk/items/ 00daa278516d1995dac6
  4. 18.

    © - BASE, Inc. テストの⾮経済性 • 維持コストが⾼いテストがテス ト⾃⾝のコストが上げていく • 可読性が悪い

    • 修正頻度が多い • 修正が難しい • テストによる節約コストも少な い効果の薄いテスト • 結果的にトータルコストが増⼤ していく ref: https://qiita.com/hgsgtk/items/ 00daa278516d1995dac6
  5. 20.

    © - BASE, Inc. まとめ:⽬的 - 「Why Testing?」 • 根本的な⽬的はコスト削減

    • xUTPの「テストの経済性」 • 費⽤対効果の⾼いユニットテストを⽬指す
  6. 25.
  7. 33.

    © - BASE, Inc. 主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  8. 35.

    © - BASE, Inc. このメソッドはどういう仕様? public function canUse(int $user_id): bool

    { $user = $this->User->findById($user_id); if (empty($user)) { return false; } if (!$user->isProhibit()) { return false; } if ($user->status === 7) { return false; } return true; }
  9. 36.

    © - BASE, Inc. ユニットテストがない場合 ΤϯδχΞ このメソッド何をし てくれるんだろう? • 動作コードを読んで理解する

    • もしあれば、ドキュメント(単体仕 様書など)を読む • 実際に動かして理解する • それでもわからない場合は、作成者 に聞きに⾏く
  10. 37.

    © - BASE, Inc. ユニットテストがない場合 ΤϯδχΞ このメソッド何をし てくれるんだろう? • 動作コードを読んで理解する

    • もしあれば、ドキュメント(単体仕 様書など)を読む • 実際に動かして理解する • それでもわからない場合は、作成者 に聞きに⾏く よくあるケース • 複雑なコードでひと⽬ではわからない • 意図通りなのかわからない
  11. 38.

    © - BASE, Inc. ユニットテストがない場合 ΤϯδχΞ このメソッド何をし てくれるんだろう? • 動作コードを読んで理解する

    • もしあれば、ドキュメント(単体仕 様書など)を読む • 実際に動かして理解する • それでもわからない場合は、作成者 に聞きに⾏く よくあるケース • そもそもない • メンテナンスされてなくて古い
  12. 39.

    © - BASE, Inc. ユニットテストがない場合 ΤϯδχΞ このメソッド何をし てくれるんだろう? • 動作コードを読んで理解する

    • もしあれば、ドキュメント(単体仕 様書など)を読む • 実際に動かして理解する • それでもわからない場合は、作成者 に聞きに⾏く よくあるケース • その対象だけを動かすのが難しい • 動かすための前準備に時間がかかる • 繰り返し実⾏するのが⼿間
  13. 40.

    © - BASE, Inc. ユニットテストがない場合 ΤϯδχΞ このメソッド何をし てくれるんだろう? • 動作コードを読んで理解する

    • もしあれば、ドキュメント(単体仕 様書など)を読む • 実際に動かして理解する • それでもわからない場合は、作成者 に聞きに⾏く よくあるケース • そもそも作成者が現場にいない • 作成者も覚えてない
  14. 41.

    © - BASE, Inc. ユニットテストがある場合 ΤϯδχΞ このメソッド何をし てくれるんだろう? • ユニットテストを読んで理解する

    • ユニットテストを実⾏して理解する • 動作コードを読んで理解する • 要件を知るためのドキュメントを読む • (それでもわからない場合)作成者に聞 きに⾏く
  15. 42.

    © - BASE, Inc. テストを読んで仕様を理解する <?php /** * @param int

    $user_id * @param bool $expected * * @see StatusChecker::canUse() * @dataProvider dataProvider_canUse */ public function test_canUse(int $user_id, bool $expected) { $checker = new StatusChecker(); $this->assertSame($expected, $checker->canUser($user_id)); } /** * @return array */ public function dataProvider_canUse(): array { return [ ‘௨ৗϢʔβʔ͸ར༻Մೳͱ൑ఆ’ => [1, true], ‘Ϣʔβʔ͕ଘࡏ͠ͳ͍৔߹͸ར༻ෆՄͱ൑ఆ’ => [9999, false], ‘ېࢭϢʔβʔͷ৔߹͸ར༻ෆՄͱ൑ఆ’ => [2, false], ‘Ϣʔβʔ͕཭୤Ϣʔβʔͷ৔߹͸ར༻ෆՄͱ൑ఆ’ => [3, false], ]; }
  16. 45.

    © - BASE, Inc. このテストは何をしている? public function testConditional() { $Flight

    = new EagerFlight(); $Flight->setNumber(1); if ($Flight->number === 1) { $this->assertTrue($Flight->cancel()); } else if ($Flight->number === 2) { $this->assertTrue($Flight->cancel2()); } if ($Flight->airline_code === 'special') { $this->assertSame(2, $Flight->number); } }
  17. 46.

    © - BASE, Inc. Simple Test • テストは読み書きする上でシンプルであるべき • ⼩さいテスト

    • 1回につき”ひとつのこと”をテストする • 重点を置くべきは、「テストを書くこと」ではなく 「テストをすること」
  18. 48.

    © - BASE, Inc. このテストはどういうパターン? <?php /** * @param int

    $user_id * @param bool $expected * * @see StatusChecker::canUse() * @dataProvider dataProvider_canUse */ public function test_canUse(int $user_id, bool $expected) { $checker = new StatusChecker(); $this->assertSame($expected, $checker->canUser($user_id)); } /** * @return array */ public function dataProvider_canUse(): array { return [ [1, true], [9999, false], [2, false], [3, false], ]; }
  19. 49.
  20. 50.

    © - BASE, Inc. テストを改善する <?php /** * @param int

    $user_id * @param bool $expected * * @see StatusChecker::canUse() * @dataProvider dataProvider_canUse */ public function test_canUse(int $user_id, bool $expected) { $checker = new StatusChecker(); $this->assertSame($expected, $checker->canUser($user_id)); } /** * @return array */ public function dataProvider_canUse(): array { return [ ‘௨ৗϢʔβʔ͸ར༻Մೳͱ൑ఆ’ => [1, true], ‘Ϣʔβʔ͕ଘࡏ͠ͳ͍৔߹͸ར༻ෆՄͱ൑ఆ’ => [9999, false], ‘ېࢭϢʔβʔͷ৔߹͸ར༻ෆՄͱ൑ఆ’ => [2, false], ‘Ϣʔβʔ͕཭୤Ϣʔβʔͷ৔߹͸ར༻ෆՄͱ൑ఆ’ => [3, false], ]; }
  21. 51.

    © - BASE, Inc. 振り返り:主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  22. 54.

    © - BASE, Inc. 主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  23. 57.
  24. 58.

    © - BASE, Inc. SUT - Sytem Under Test •

    テストしている対象を⽰す • ユニットテストの場合、テスト対象のクラス‧メ ソッドなど • 書籍『xUnit Test Patterns: Refactoring Test Code』で登場する⽤語 https://www.amazon.co.jp/dp/ /ref=cm_sw_r_tw_dp_U_x_Y kJCb EX F
  25. 61.

    © - BASE, Inc. テストがSUTに密結合する弊害 • SUTの変更に敏感に反応してテストが落ちる • ex. Private

    methodの実装変更 • SUTの内部実装を変えるたびに、テストを修正する • 壊れやすいテスト • 維持コストを抑えるために疎結合を意識する
  26. 63.

    © - BASE, Inc. Test Public Method • SUTをブラックボックスとして⾒る •

    ブラックボックスの外から⾒える Public Method に 対してテストするのが Better • 逆を返すと、Private Methodはテストしない⽅が Better • ブラックボックスの外から⾒えない
  27. 64.

    © - BASE, Inc. 尺度:Methodの “安定度” • Public Method •

    外部から使⽤される想定を持つ • 変更の頻度が少ない、“安定”している • Private Method • 外部から使⽤される想定はない • 責務の “不安定” さ • 変更の頻度が⾼い、 “不安定”
  28. 65.

    © - BASE, Inc. Do Not Test Private Method •

    変更の頻度が⾼い Private Method • → テストの修正の頻度も⾼くなる • →メンテナンスコストの増⼤ • Private Methodは、同⼀クラスのPublic Methodに よって使⽤される • → Public Testのテストでカバーできる
  29. 66.

    © - BASE, Inc. IF YOU CAN, Do Not Test

    Private Method • 現実の “レガシーコード” を⽬の前にするとテストし たいときはある • Private methodを切り出していくのが Better • それも苦しい時には、リファクタリングの障壁をへら すために、テストを書く選択肢もひとつある
  30. 68.

    © - BASE, Inc. 突然テストが落ちるようになりました、なぜ? public function test_canUse(int $user_id, bool

    $expected) { $search = CloudSearchWrapper::factory(); $search->conditions() ->keyword(‘test’, ‘title’) ->sort(‘list_order asc’); $search->documentBatch($this->addJson()); sleep(3); $result = $search->search($search->conditions()->query()); }
  31. 69.

    © - BASE, Inc. Isolate SUT from others • SUTを他のソフトウェア(外部システム)から隔離す

    る • 依存している場合 • 外部システムの動作変更‧状態に応じて、テストが 突然落ちる • Dependency Injectionなどの技法の活⽤ • 依存しているオブジェクトをTest Doubleに置き換 える
  32. 72.

    © - BASE, Inc. テスト間の独⽴性 • テストが相互依存‧順序依存する場合、 “独⽴性”が ない状態 •

    “独⽴性” がない弊害 • テストが通ったり落ちたりする • 「ローカルでは通るがCIでは通らないことがある」 • 不安定なテスト
  33. 76.

    © - BASE, Inc. Depend in Fixture properly • データベースを⽤いるテストを⾏う場合

    Fixture を⽤ いる • 全テストケースで共有する 共有されたFixture • 共有しているためテストの追加‧変更に影響を受ける
  34. 80.

    © - BASE, Inc. 共有されたFixtureのMerit/Demerit • Merit • 共通のマスター系テーブルであれば、⼀回の Fixture作成で済む

    • Demerit • テスト間の独⽴性が損なわれる • エッジケースのテストのやりにくさ
  35. 83.

    © - BASE, Inc. Fixtureに適切に依存する • 全テストで共有することが有益なケース • 基本となるFixtureを定義 •

    ex. 「通常のユーザー」 • 使いみちが限定されるエッジケースのテスト • テストケース専⽤Fixtureを⽤意する • ex. 「利⽤禁⽌になったユーザー」
  36. 85.

    © - BASE, Inc. Tear Down Global Changes • テストがグローバルな状態変化を⽣み出す場合

    • $_SERVERの書き換え • Session状態の更新 • トランザクション • テスト時間 (timecop使⽤の場合など) • 他のテストに影響を及ぼす • テスト‧SUTの状態変化に応じてtearDownしておく
  37. 86.

    © - BASE, Inc. 振り返り:主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  38. 89.

    © - BASE, Inc. 主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  39. 91.

    © - BASE, Inc. DRY in Test Code • DRY

    (Don’t Repeat Yourself)は、テストコードにお いても同じく意識 • テストコードの可読性を上げる • フレームワーク‧カスタムのTest Utilityの活⽤によ り、より意図を語るテストへ
  40. 92.

    © - BASE, Inc. 振り返り:主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  41. 95.

    © - BASE, Inc. 主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  42. 98.

    © - BASE, Inc. TDD - Test-Drive Development • プログラム開発⼿法のひとつ

    • 「動作するきれいなコード」をゴールとする • Red-Green-Refactor のサイクルを回す • 最初に Red にするため、テストファーストで進める
  43. 101.

    © - BASE, Inc. 振り返り:主に期待する効果 Ϣχοτςετ ʹΑΔ ίετ࡟ݮ Ϣχοτςετ ͷ

    ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ • υΩϡϝϯςʔγϣϯίετ • σόοάίετ • ઃܭվળʹΑΔϝϯςφϯείετ • ৽نςετ࡞੒ίετ • طଘςετҡ࣋ίετ • ςετ࣮ߦ࣌ؒͷ଴ͪίετ • ࣗಈςετͷͨΊͷCIҡ࣋ίετ • Ϣχοτςετͷֶशίετ
  44. 107.

    © - BASE, Inc. 補⾜:参考書籍 『xUnit Test Patterns: Refactoring Test

    Code』 https://www.amazon.co.jp/dp/ / ref=cm_sw_r_tw_dp_U_x_Y kJCb EX F 『オブジェクト指向設計実践ガイド 〜Rubyでわかる 進化しつづける柔 軟なアプリケーションの育て⽅』 https://gihyo.jp/book/ / - - - -
  45. 108.

    © - BASE, Inc. 補⾜:関連資料 xUnit Test Patternsから学ぶ12個のユニットテストの原則 https://qiita.com/hgsgtk/items/a a

    d d b d xUnit Test Patternsから学ぶユニットテストの6つの⽬指すべきゴール https://qiita.com/hgsgtk/items/ daa d dac xUnit Test Patternsから学ぶテストアンチパターン https://speakerdeck.com/hgsgtk/testing-anti-pattern-learned-in-xunit-test- pattern ユニットテスト初⼼者を脱するために⾝につけたいN個のこと https://speakerdeck.com/hgsgtk/n-points-to-get-out-of-unit-test-beginner- number-phpstudy
  46. 109.

    © - BASE, Inc. 補⾜:関連資料 テストが⾟いを解決するテスト駆動開発のアプローチ https://speakerdeck.com/hgsgtk/tesutokaxin-iwojie-jue-surutesutoqu-dong- kai-fa-falseahuroti-at-phpkanhuarensuxian-tai- PHPバージョンアップと決済リプレイスを⽀えたユニットテスト https://speakerdeck.com/hgsgtk/phpbaziyonatuputojue-ji-ripureisuwozhi-

    etayunitutotesuto-number-phpcon テストを書いたことがないエンジニアがテストを書けるようになるまで やったこと https://speakerdeck.com/hgsgtk/tesutowoshu- itakotokanaiensiniakatesutowoshu-keruyouninarumateyatutakoto-at- phpkanhuarensuguan-xi-