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

『テスト書いた方が開発が早いじゃん』を解き明かす #phpcon_nagoya

『テスト書いた方が開発が早いじゃん』を解き明かす #phpcon_nagoya

PHPカンファレンス名古屋2025の発表資料です
https://fortee.jp/phpcon-nagoya-2025/proposal/436ec84b-1ab6-46a4-abb8-a29abaf8a817

hideki kinjyo

February 21, 2025
Tweet

More Decks by hideki kinjyo

Other Decks in Programming

Transcript

  1. イントロ  [ग़య] ᴷ Meszaros, Gerard. ʰxUnit Test Patterns: Refactoring

    Test Code (Addison-Wesley Signature Series (Fowler)) Kindle dition. ʱP20
  2. 自己紹介 ✔ 紹介済み • 金城秀樹 / きんじょうひでき • GitHub: @o0h

    / 𝕏 : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • これで3年連続・4度目の名古屋です 
  3. ① 複雑さ: 考えることが多くて、とっても複雑  「有効なユーザー名」の ルールを確認しなきゃ 判定は正規表現? バリデーションのラ イブラリって入ってるんだっ け?

    このルールを使うのはここ だけ? 失敗したらfalse? 例外投げる? 事前にチェックされ てるのは?nullも来る?string だけ? メソッドを切るのが良いのか な、インラインでいいのかな 引数名はどうしよう 違反内容はどこまで記述する? メッセージはUIにそのまま出 される?別で解釈される? 文言、ですます長だっけ?
  4. ① 複雑さ: 考えることが多くて、とっても複雑  「有効なユーザー名」の ルールを確認しなきゃ 判定は正規表現? バリデーションのラ イブラリって入ってるんだっ け?

    このルールを使うのはここ だけ? 失敗したらfalse? 例外投げる? 事前にチェックされ てるのは?nullも来る?string だけ? メソッドを切るのが良いのか な、インラインでいいのかな 引数名はどうしよう 違反内容はどこまで記述する? メッセージはUIにそのまま出 される?別で解釈される? 文言、ですます長だっけ? ビジネス 「ビジネスルール」、「コーディングガイドライン」、 「他の箇所との一貫性」、「設計レベルの判断」、「細 やかな可読性や堅牢性」etc.. ⇑ 様々なレイヤーの判断が押し寄せる
  5. ② 緩慢さ: 制約が「ソフト」で、逸脱や違反に対して鈍感  $me->cut($cake, -10000) みんなに ケーキを切り分けよう! ビジネス 現実なら「意識する前から存在していた法則」が

    物理世界に留まるようにガイドするが、 制約はプログラマが恣意的に与える必要がある。 ⇓ 「自然」がなく、常に思考に負荷が掛かる
  6. ③ 曖昧さ: 実現したいことに対して、実装方法や完成度が曖昧 Q. 「買い物にいって牛乳を1つ買ってきて。卵があったら6つお願い」  牛乳は ハードコーディングで良い? 卵はキャッシュから使い回 せる?だめ?

    (プログラミングにおいては)問題が明確になりにくく、 解法は更に多くのバリデーションを発生させる 買い物に行くのはログイン が必要?匿名で良い?
  7. ③ 曖昧さ: 実現したいことに対して、実装方法や完成度が曖昧 Q. 「買い物にいって牛乳を1つ買ってきて。卵があったら6つお願い」  牛乳は ハードコーディングで良い? 卵はキャッシュから使い回 せる?だめ?

    (プログラミングにおいては)問題が明確になりにくく、 解法は更に多くのバリデーションを発生させる 買い物に行くのはログイン が必要?匿名で良い? ビジネス 「現実」から「論理」に翻訳するということは、 本質的に「形を変えて表現する」作業となる。 出題の意図と回答が、"==="で結びつかない世界。 ⇓ ゴールを「固定」しないと完成に到達しない
  8. 複雑さをテストで易しくする例  平均ってことは、 まずは全部を足して 要素数で割ればOK? Collection::fromArray([1, 10, 10])->avg() === 7

    function avg(): float { $total = array_sum($this->items); $count = count($this->items); return $total / $count; }
  9. 複雑さをテストで易しくする例  int/floatだけとも 限らないよな try { Collection::fromArray([1, 'a'])->avg(); } catch

    (UnexpectedValueException) { ɾɾɾ if (!array_all( $this->items, fn($v) => is_float($v) || is_int($v), )) { throw new UnexpectedException; }
  10. 緩慢さをテストで取り締まる例  求人案件の登録で、 提示年収を管理する機能 年収って言ったら 300ドングリ以上だよね 最近はドングリの代わりに ハマグリを渡す会社も多いみたいだし、 対応して対応しておこう 偽ドングリを

    渡そうとしたら駄目だぞ 明らかに 会社の資本力と見合っていない ドングリやハマグリを受け付けるべきでない ハマグリを使うなら 予め「ハマグリ輸送手段」が 設定されているべきだ
  11.  namespace App\Util; class Collection { public function avg() {

    } } 自分で素朴なテストを用意してみる こんなコードが あったとしたら
  12.  use App\Util\Collection; require '../vendor/autoload.php'; $obj = new Collection([1, 10,

    22]); assert($obj->avg() === 11); 自分で素朴なテストを用意してみる autoload.phpだけ読み込 んで、 オブジェクトをnewして、 メソッドを呼び出して、 assert()する コードがあればOK! 必要に応じてvar_dump() なども書き加える。
  13.  use App\Util\Collection; require '../vendor/autoload.php'; $obj = new Collection([1, 10,

    22]); if ($obj->avg() !== 11) { throw new \Exception('failedʂ'); } 自分で素朴なテストを用意してみる もっと愚直な感じでも 何も問題ない。
  14.  public function registerAction( Request $request, UserSaveService $service, ) {

    // ԿΒ͔ͷॲཧ } プロダクトコードの中にテストを同居させてしまう 今っぽいFWだと、 コンストラクタやメソッ ドで依存を注入しがちで すよね
  15.  public function registerAction( Request $request, UserSaveService $service, ) {

    // ԿΒ͔ͷॲཧ assert(ɾɾɾɾ); } プロダクトコードの中にテストを同居させてしまう 思い切って、そのアク ションの中に検査(テス ト)を書いてしまう ※ もちろん、メソッドだ けは別にしてもOK
  16.  public function registerAction( Request $request, UserSaveService $service, ) {

    // ԿΒ͔ͷॲཧ assert(ɾɾɾɾ); } プロダクトコードの中にテストを同居させてしまう そうすれば、 複雑な「生成」「起動」 の知識がなくても、 いつも通りに動かすだけ でテストができる
  17.  public function registerAction( Request $request, UserSaveService $service, ) {

    // ԿΒ͔ͷॲཧ assert(ɾɾɾɾ); } プロダクトコードの中にテストを同居させてしまう ただし、コードが別ファ イルで管理されている場 合と違って、 保存して→蓄積していく のが難しい。。。 実装中のテストに限定し て適用できる方法。
  18.  public function __construct($items) { $this->items = (array)$items; assert($this->avg() ===

    12); } プロダクトコードの中にテストを同居させてしまう クラスの設計や機能に よっては、コンストラク タの中に埋め込むことで 検査を強制させるのも便 利な方法。
  19.  public function __construct($items) { $this->items = (array)$items; assert( $this->f1()

    === $this->f2() ); } プロダクトコードの中にテストを同居させてしまう リファクタ時には、「前 のメソッドを完全コピ ペ」「新メソッドと結果 を一致させる」などの使 い方もできる
  20. もっと複雑な場合: APIレスポンスをテストする • 例えばAPIの機能改修を行う場合には・・ • 改修前に、そのエンドポイントのスナップショットを用意して • 改修後の「変わるべき場所」の検査を追加する  $actualResponse

    = file_get_contents('http://localhost/hoge'); $oldResponse = file_get_contents('old_response.json); // ͍͍ͬͯ͡ͳ͍৔ॴ͕ਖ਼ؚ͘͠·Ε͍ͯΔ͜ͱΛ͔֬ΊΔ assert(empty(array_diff( json_decode($oldResponse, true), json_decode($actualResponse, true), ));
  21. ② 書いてもgit commitしなければ自分だけの世界 • 書いたコードを、必ずしもpushする必要はない • 「消す前提のコード」を書いても良い • t-wadaさん「学習用テスト」 •

    資産として残しておきたいなら、`.git/info/exclude` もおすすめ • 自分だけの.gitignoreみたいなもの。 • ここに学習用テストや、スニペット等をおいておくと便利になるかも 
  22. ③ 自分が書いたテスト以外を「使う」 • 通常のコードと同じく、 「書く」のと同じくらい「たくさん読む」ことも重要 • 普段利用しているフレームワークやライブラリのテストコードを よく読んでみることで、一石二鳥の経験値を獲得できる • 手元で動かしてみると更に◎

    • その際に、パラメータなどを書き換えてみることで実装の理解もしやすくなる • 「この値を受け取った時にどうなるんだろう」という興味本位で 弄り放題、壊し放題!なのは楽しいしオススメ 
  23. 興味を持った人向け: べりべりオススメリソース • TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング

    https://www.youtube.com/watch?v=Q-FJ3XmFlT8 • とにもかくにも、「テストを味方にして開発を楽にする」が全部ある動画 • ソフトウェア品質を高める開発者テスト 改訂版 アジャイル時代の実 践的・効率的でスムーズなテストのやり方(高橋 寿一)|翔泳社の本 https://www.shoeisha.co.jp/book/detail/9784798176390 • 開発経験のある人が「テストってどういう感じ」に初めて触れるのに良い感じ