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

PHPUnit 9時代のTest Doubleの作り方 / deep dive into mockbuilder of phpunit 9

PHPUnit 9時代のTest Doubleの作り方 / deep dive into mockbuilder of phpunit 9

PHPUnit もバージョン 9 になりました。長年第一線で活躍し私達PHPerにテスト可能な開発環境を提供し続けてくれているPHPUnitは、その分歴史が長く、バージョンごとに これまでの使い方は deprecated になっていきます。

PHPUnit 9 では、さまざまな機能削除や非推奨勧告がありますが、その一つが表題にある MockBuilder::setMethods の deprecated です。

そこで改めてこの変更を機に、次の点を抑えていきます。

- PHPUnitのTestDoubleの語彙整理の根拠 xUTP本とその該当箇所、createStub / createMockの違い
- setMethodsからaddMethods/onlyMethodsになった使い方
- deprecatedになった理由をissueの内容から抑えて背景思想を理解する
- PHPUnit の内部実装ではどのような変更が行われたのか(これはPHPUnit 8でのaddMethods/onlyMethodsの話でもある)

88964b936e864ca7d326272eaa70fa9a?s=128

Kazuki Higashiguchi

March 28, 2021
Tweet

Transcript

  1. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. #xUTP #xUnit

    #PHPUnit PHPUnit 9 時代の Test Doubleの作り方 1 2021.03.26 ~ 03.28 PHPerKaigi2021 @hgsgtk Kazuki Higashiguchi
  2. © 2012-2021 BASE, Inc. 2 PHPUnitの使い方でちょこっと ( ・´ー・`)どやれる(かもしれない) 知識

  3. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 当発表で得られるもの 1

    2 3 MockBuilder::setMethodsがdeprecatedになった背 景を理解する PHPUnit 9 で変わったTest Doubleの作り方をユー ザー視点で理解する モックオブジェクト作成についてPHPUnit 10 の見 通しを知る 3
  4. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. @hgsgtk Kazuki

    Higashiguchi 東京に住まう Software Developer / BASE BANK, Inc. Manager, Tech Lead 自己紹介 4 (2020.12) Dockerアプリケーショ ン開発実践ガイド > コンテナアプリケー ションの設計セオリー (2019.12) PHPにおけるユニット テストとCI/CD > ユニットテストの育 て方 PHPerKaigi Archives 2021.3.27: 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 2020: 自作して理解するxUnit 2019:「質」のいいユニットテストを書くためのプラクティス 2018: レビューをもらいやすい細かいプルリクの切り分け方
  5. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPUnit 9

    • PHPerのためのテスティングフレームワーク、xUnitアーキテクチャ・ファミリの一つ ◦ https://github.com/sebastianbergmann/phpunit • 2020年2月7日に PHPUnit 9 がリリース ◦ https://phpunit.de/announcements/phpunit-9.html • PHPUnit 9の特徴 ◦ PHP7.3 >= ◦ いくつかの破壊的変更 (ex. assertEquals 等のパラメータ) ◦ いくつかのAPIの非推奨化(ex. expectException) 5
  6. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 余談: PHPUnit

    Versions 6 https://phpunit.de/supported-versions.html
  7. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPUnit 9のTest

    Doubleの作り方に影響する話 MockBuilder::setMethods() の deprecated 7
  8. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 補足: “Test

    Double” • 『xUnit Test Patterns: Refactoring Test Code』(通称: xUTP)にて定義され た語彙整理、テスト固有の同等物を指す ◦ http://xunitpatterns.com/Test%20Double.html • 一般的によく使われるMockはTest Doubleのことを言っていることが多い(”広義 のMock”) 8
  9. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Pull Request

    #3687 [Summary] • onlyMethodsとaddMethodsが追加 されました(リリース: PHPUnit 8.5.0) • setMethodsが(soft) deprecatedと なりました(リリース: PHPUnit 9) 9 • MockBuilder::setMethods() を deprecated にするPR (2019/03/10) ◦ https://github.com/sebastianbergmann/phpunit/pull/3687
  10. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPUnit 9以前のテストダブルの作り方例

    10 MockBuilder::setMethods()でモックオブジェクトにメソッドを設定していた
  11. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPUnit 9時代のテストダブルの作り方:未定義

    11 未定義メソッドはMockBuilder::addMethods()で
  12. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPUnit 9時代のテストダブルの作り方:定義済み

    12 定義済みメソッドはMockBuilder::onlyMethods()で
  13. © 2012-2021 BASE, Inc. 13 ( ・´ー・`)どや

  14. © 2012-2021 BASE, Inc. 14 ( ' ^'c彡☆))Д´) パーン

  15. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. addMethods/onlyMethods の背景

    15 @DFoxinator によるPR#3687の意図説明(私訳、サマリー) • setMethods()の場合”クラス内のメソッド存在”が保護されていない • メソッドを削除してもsetMethods()で作ったテストは通ってしまう • createMock()は、厳密に「存在しないメソッドをモックしようとしたこと」を教 えてくれることも相まって、動作に混乱が生まれている • より厳密な機能がほしいユーザー向けに後方互換性を保つ形で機能を提供した • => setRealMethods ってどうですかという提案 最初 は”setRealMethods”って いう名前で 提案されていたんだね
  16. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. createMockの厳密な振る舞い 16

    • createMockでは、ここのデフォルトの振る舞いが 厳密になっていてエラーになります • setMethodsで、消えたメソッドに期待値設定して いると、実際のクラスから消えた後も気が付かずに テストが通る『夢の世界に生きるテスト』(※1) になる 「PHPUnitはメソッド消えた時はエラーで落ち てくれるよ」という話は厳密には偽です 「createMock使っていれば」が正確です ( ' ^'c彡☆))Д´) パーン ※1 『オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつ づける柔軟アプリケーションの育て方』 9.2 受信メッセージをテストする
  17. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 具体的なユースケース想定 17

    • 「みんなcreateMock使ってるのでは?」というsebastianbergmannのリアク ションもあった(みなさんどうですか?) • 今回のPRにおける具体的な使用例としては、クラスの一部のメソッドをモックす るシーン ◦ その場合はcreateMockの対応しないユースケースである
  18. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. createPartialMockによるモックはどうか 18

    • createPartialMockがpartial mockingの用途で使えるが、これも setMethods を 内部で使用している(PHPUnit 9現在) ◦ ゆえに setMethods に対して指摘された問題は回避できない • PHPUnit 10に向けたmasterブランチはonlyMethodsを使うようになっている ▪ https://github.com/sebastianbergmann/phpunit/issues/3769 ▪ https://github.com/sebastianbergmann/phpunit/commit/baa0ccee4c1eb3f697fec256059119c8874b949d
  19. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. addMethods/onlyMethods となる

    19 setMethodsに対する後方互換性の維持 と移行戦略 内部実装も徐々に置き換えていく構想 from 8 -> 10
  20. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. PHPUnit 10にてsetMethodsは削除される

    20 https://github.com/sebastianbergmann/phpunit/blob/dc2d6c4e2fdd9d53606d2a578de0ba65a162ed5b/ChangeLog-10.0.md Changes in PHPUnit 10.0ではすでに削除が明示されています
  21. © 2012-2021 BASE, Inc. 21 Dive deep into PHPUnit

  22. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. Impl: @deprecated

    setMethods() https://github.com/sebastianbergmann/phpunit/pull/3687 22 src/Framework/MockObject/MockBuilder.php にマーキングが追加される
  23. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 後日談: mock設定有無判別のリファクタリング

    src/Framework/MockObject/MockBuilder.php https://github.com/sebastianbergmann/phpunit/pull/3687 23 変更の理由が同じpropertyならば、boolなflag propetryよりnullable propertyにする選択肢 をとったほうがシンプルになる
  24. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. MockBuilder::addMethods() の責務

    https://github.com/sebastianbergmann/phpunit/blob/943003a5b2bd1292eb5f202c41be34770284d214/src/Framework/MockObject/MockBuilder.php#L27 24 1. ReflectionClassからメソッドが存在する かをチェックする ◦ 存在する場合はaddMethodsの利用シー ンではないため例外 (CannotUseAddMethodsException) ◦ onlyMethods()の実装はここが逆の判定 2. mock objectのメソッドに追加する
  25. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 余談: テストコードから読む例外ハンドリング

    https://github.com/sebastianbergmann/phpunit/blob/c8be2686e0f97fa648806a516f10f889e5a0be45/tests/unit/Framework/MockObject/MockBuilderTest.php#L75 25 • 存在するメソッドの場合・存在しない場合両 方でテストを用意 微妙なユースケースはテストコード を読むと挙動が明らかになるよ
  26. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. 参考文献 26

    https://www.amazon.co.jp/gp/product/B004 X1D36K?ref=dbs_p2d_P_R_kindle_available_ T1 https://www.amazon.co.jp/dp/4274217884 https://www.amazon.co.jp/dp/B01L8SEVYI
  27. © 2012-2019 BASE, Inc. © 2012-2021 BASE, Inc. まとめ •

    PHPUnit 9 でsetMethods 使っていた人は onlyMethods/addMethods に変えま しょう ◦ PHPUnit 10 で setMethods は削除されます • createMock を使うと脳死で onlyMethods と同等の振る舞いが得られる • createParitialMock を使うと PHPUnit 9 までは setMethods と同じ振る舞い ◦ PHPUnit 10 になるとこちらを活用すると良いでしょう • 最初は setRealMethods って名前で提案されてたんだよっていう豆知識を” ( ・´ー・`) どや”って “( ' ^'c彡☆))Д´) パーン” されましょう
  28. © 2012-2021 BASE, Inc. Thanks at #phperkaigi