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

テストコードを導入して変わったこと 〜秘伝のタレ状態からの脱却〜

Avatar for uiui uiui
November 24, 2025
150

テストコードを導入して変わったこと 〜秘伝のタレ状態からの脱却〜

2025年11月24日PHPカンファレンス香川にて

Avatar for uiui

uiui

November 24, 2025
Tweet

Transcript

  1. 株式会社クレアヴォイアンス SWE 
 エンジニア歴4年 
 自己紹介
 uiui
 X: @U_Okuyama_Devlp work:最近はAWS/Laravel/Vue あたりでやっています。

    また生成AI系をWebアプリの機能に組み込むみたいなのが 多いです。 それ以外:元々ヴァイオリン弾きです。最近は日曜音楽家と名乗ることにしました。 あと辛い物が好き!
  2. コストをかけられる人だ けが書くもの じゃないの? テスト書くの コスト高いよね? なくても動くから後 回しでいい? すごく高い信頼性を 求められている ものだけ?

    テスト書くより 実装を進めたい… 難しそう… 時間かかりそう めんどくさそう テストを書かないチームで抱きがちな印象
  3. $shipping = ($item->isFreeShipping()) ? 0 : 1000; if (!is_null($item->customShippingFee())) {

    if ($user->livingInSpecialPlace()) { //特殊地域 if ($item->customShippingFee() + self::SPECIAL_PLACE > 3000) { //3000円以上だったら割引をする $shipping = $item->customShippingFee() + self::SPECIAL_PLACE - self::HIGHER_DISCOUNT; } else { $shipping = $item->customShippingFee() + self::SPECIAL_PLACE; } } else { $shipping = $item->customShippingFee(); } } if ($user->isPremium()) { if ($user->livingInSpecialPlace()) { $shipping -= 500; } else { $shipping = 0; } } *実際のコードではありませんが形はこんな感じ 🙃仕様の割にコードが長くて複雑 (ネストの深いif文もよく見られる) もっとスマートに書けるはずなのに どうしてこう書かれてるのかわからない
  4. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; if (!is_null($item->customShippingFee()))

    { if ($user->livingInSpecialPlace()) { //特殊地域 if ($item->customShippingFee() + self::SPECIAL_PLACE > 3000) { //3000円以上だったら割引をする $shipping = $item->customShippingFee() + self::SPECIAL_PLACE - self::HIGHER_DISCOUNT; } else { $shipping = $item->customShippingFee() + self::SPECIAL_PLACE; } } else { $shipping = $item->customShippingFee(); } } if ($user->isPremium()) { if ($user->livingInSpecialPlace()) { $shipping -= 500; } else { $shipping = 0; } } … 🙃責務や粒度が適切でないコード構造 メソッドに切り出さないがゆえに 複雑になってることも多々あり メソッドに切り出すべき …
  5. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; if (!is_null($item->customShippingFee()))

    { if ($user->livingInSpecialPlace()) { //特殊地域 if ($item->customShippingFee() + self::SPECIAL_PLACE > 3000) { //3000円以上だったら割引をする $shipping = $item->customShippingFee() + self::SPECIAL_PLACE - self::HIGHER_DISCOUNT; } else { $shipping = $item->customShippingFee() + self::SPECIAL_PLACE; } } else { $shipping = $item->customShippingFee(); } } if ($user->isPremium()) { if ($user->livingInSpecialPlace()) { $shipping -= 500; } else { $shipping = 0; } } … 時がたつと… 🙃どのパターンをテストすればいいかわからない
  6. public function sendNotification(array $user, bool $isEmail) { … //メッセージの整形など様々処理 if

    ($isEmail) { // メール用処理 … mail($user['email'], "通知", $message); } else { // SMS用処理 … smsSend($user['phone'], $message); } … //メッセージ送信後の様々な事後処理 Log::info("送信完了: " . ($isEmail ? 'Email' : 'SMS') . "\n"); } 🙃共通化すべきでない処理の共通化
  7. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; … 事例①

    *実際のコードではありません *むちゃくちゃ手続型でした 最初はシンプルだった
  8. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; if ($user->isPremium())

    { $shipping = 0; } … 仕様が追加されていく 事例① *実際のコードではありません
  9. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; if (!is_null($item->customShippingFee()))

    { $shipping = $item->customShippingFee(); } if ($user->isPremium()) { $shipping = 0; } … 仕様が追加されていく 事例① *実際のコードではありません
  10. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; if (!is_null($item->customShippingFee()))

    { if ($user->livingInSpecialPlace()) { $shipping = $item->customShippingFee() + self::SPECIAL_PLACE; } else { $shipping = $item->customShippingFee(); } } if ($user->isPremium()) { if ($user->livingInSpecialPlace()) { $shipping -= 500; } else { $shipping = 0; } } 仕様が追加されていく 事例① *実際のコードではありません
  11. … $shipping = ($item->isFreeShipping()) ? 0 : 1000; if (!is_null($item->customShippingFee()))

    { if ($user->livingInSpecialPlace()) { if ($item->customShippingFee() + self::SPECIAL_PLACE > 3000) { //3000円以上だったら割引をする $shipping = $item->customShippingFee() + self::SPECIAL_PLACE - self::HIGHER_DISCOUNT; } else { $shipping = $item->customShippingFee() + self::SPECIAL_PLACE; } } else { $shipping = $item->customShippingFee(); } } if ($user->isPremium()) { if ($user->livingInSpecialPlace()) { $shipping -= 500; } else { $shipping = 0; } } … 現在に至る
  12. public function sendNotification(array $user) { // メール処理 mail($user['email'], "通知", $message);

    } 事例② *実際のコードではありません 最初はシンプルだった
  13. public function sendNotification(array $user) { … //メッセージの整形など様々処理 // メール用処理 …

    mail($user['email'], "通知", $message); … //メッセージ送信後の様々な事後処理 Log::info("送信完了: " . ($isEmail ? 'Email' : 'SMS') . "\n"); } 事例② *実際のコードではありません 例によって複雑化し
  14. public function sendNotification(array $user, bool $isEmail) { … //メッセージの整形など様々処理 if

    ($isEmail) { // メール用処理 … mail($user['email'], "通知", $message); } else { // SMS用処理 … smsSend($user['phone'], $message); } … //メッセージ送信後の様々な事後処理 Log::info("送信完了: " . ($isEmail ? 'Email' : 'SMS') . "\n"); } 事例② *実際のコードではありません 仕様の追加により 
 切り出しや抽象化が必要になるが 
 変更できない 

  15. function calcShipping(int $amount): int { // 3000円以上の購入なら送料無料 if ($amount >=

    3000) { return 0; } return 500; } //3000円以上が送料無料になるか // ① 準備(Arrange) $amount = 3500; // ② 実行(Act) $shipping = calcShipping($amount); // ③ 検証(Assert) if ($shipping === 0) { echo "OK: 3000円以上は送料無料"; } else { echo "NG: 送料無料になっていない "; } ざっくり言うと 決めた入力を与え、期待した値になっているかを確かめるもの すごくシンプル テスト対象
  16. テストがない時の開発 
 󰢨(初心者) 2〜3行書く→デバッグ 󰟻(ちょっと慣れた開発者 ) ほとんど書いてからデバッグ 一発で動いたらラッキー。 しかしほとんどの場合は、どこでバグが出てるのか調査に時間を費やす ことになる

    🤓(だいぶ慣れた開発者) 数十行書く→デバッグを繰り返しながら開発する。 ある程度コードを書くと、デバッグしてバグが出た時に調査に時間がかかりそう だという直 感が働くようになる。 しかしデバッグにも時間がかかる のでバランスを意識し始める *個人的な所感によるものです TDDが短期的にも効果的な理由①
  17. 対処法:仮説検証を早く回す 
 TDDはインターフェースから考えることになる #[TestDox("要約の取得")] public function test_summary(): void { $frame

    = new TextMessageFrame(text: TextMessageFrame::LINK_TAG . "Hello! Test!"); $expected_summary = "[リンク]Hello! Test!"; $this->assertEquals($expected_summary, $frame->getSummary()); } 先にメソッド名や引数・期待する戻り値などを決める その後中身を実装する TDDはインターフェースから考えることになる TDDが短期的にも効果的な理由②
  18. 😎TDDはインターフェースから考えることになる② 
 • 「作るものは何か」から考えることになる • まず使いやすく・誤りにくいメソッドの外観を考える 習慣がつく(名前・入 力パラメーター・戻り値など) また、テストを書くことで実装よりも インターフェースに集中することに

    なります(これは常に良いことで す)。 p.91 開発者はまず「作るものは何か」から始める べきだ。 〜 実装についての知識がシステムの他の部 分に漏れるのを防ぐ・コードの焦点を集中さ せられるし、カプセル化もできるようになる。 p.175
  19. テストコード・TDDのメリットまとめ 
 • リファクタリングができる • 想定するテストケースが残っている • 適切な責務・粒度の指標になる • 変更をする勇気を与えてくれる

    • エラーを早期発見できデバッグ時間が短くなる • 仕様や使い方も示すことができる • TDDで着実に前進できるようになる • TDDでインターフェースをより意識できる • TDDでモチベが高い状態でテストコードを書ける
  20. (補足)テストが書ける環境だったのか? 
 この段階では • 自身のローカルだけでもいい ので書けそうなところを探す • 自身が書く新規コード があれば、テストを先に書きつつ実装すると良い •

    関数に切り出されていて外部のAPI等に依存してなければ書けそう • プロダクションコードに影響はないのでチームメンバーもスルーしていた • DBの依存はあったが、とりあえずは後でデータも消すようにテストコードを書けば いいので試してみるくらいであれば問題ない
  21. 意識すること 
 • やることが決まっても積極的にサポートをしなければ廃れてしまう (人間は元の やり方で続けたくなるものだ) • 現状維持バイアス 的な意見が出ることも覚悟し、それを否定しない •

    各メンバーがテストコードによる恩恵を自分で発見 できるように、サポートする (機会をつぶさない・誘導する) • 書くことで改善できそうな場面を見つけたら、すかさず&さりげなくサポートする