【読書】実践テスト駆動開発
計画は立てれば立てるほど良い。もっというと日毎計画なんてもっと立てろ
この世の一般的なシステムは変化し続ける。
システムが変化すると、予期せぬエラー発生確率が上がる。
しかし、この事象は仕方のないことである。
エラー発生確率が上がるからといって、変化を止めることはできない。
だから、変化により起こりうるバグリスクを如何に下げるかということに集中していきたい。
システムの予期せぬエラー対処法
1. 回帰テストが機能するようにしておく
何かを変更したり、機能追加した際でも、大きなバグが起きてないか自動でチェックしたい。
2. コードをできる限りシンプルにする。可読性を上げる。
3. リファクタリング
変化するシステムにおいては、その状況によって最適な状態が変わる。
変化前までは最適だったコードベースも、変化後には少し腐っているということが起きる。
例えば、「3つ以上同じような処理がある箇所を共通化しない」ことで、それが負債化していくとか。
ディレクトリ構造を変えないといけなくなったとか。
などなど
最適でない状況は「システムの負債」となり、この負債が積み上がるごとに「変化しにくい」「バグりやすい」システムが生まれる。
そうならないよう、少しでもいいから変化の後には「リファクタリング」を行って負債回収を行っていくべきである。
TDDを最大限活かすなら、着手するフィーチャに対する「受け入れテスト」を先に作ること フィーチャ(ex:本を登録する)のテストケースを書き出して、それを受け入れテストコードとして実装しておく。
最初はこれらのコードは失敗するが、内部でTDDを使って実装していくことで、徐々にGREENになり始める。
という流れ。
どんな領域でも作業前は必ず「ゴールを明確にする」という行為は重要だということです。最重要事項です。
「システムが常に十分機能するのか?」と言えるための3つのテスト
1. 受け入れテスト:システム全体が機能するか
2. インテグレーションテスト:私たちが変更できないコードに対して、書いたコードが機能するか
外部のシステムと切り離した状態で、自分たちのコードが正しく動くのか
3. ユニットテスト:オブジェクトは正しく振る舞っていうrか。オブジェクトは扱いやすいか
具体的な名称は正直なところどうでもいい。
重要なのは...
「フィードバックループ」をシステムの各レベルで用意しておくということ
一番外側の実装が関係しない機能的な言葉でできたテストだったり、内部の実装に近い場所のテストだったり。
本当のTDD(素早く質高く、インクリメンタルにシステムを開発する手法)
コード実装よりもまず「ビルド・デプロイ・テスト」の自動化を先に構築する
これには大きな利点がいくつかある
1. リグレッションテストが出来上がることで、開発途中もシステムが正常に動くことを自動チェックできる
2. デプロイというエラーが発生しやすいプロセスを先に組んでおくことで、プロジェクトのリスクを下げる
3. 本番想定のデプロイによって、関わる必要のある外部システムやチームがPjt初期段階で見えやすくなる
しかし、ビルド/デプロイも何も、実装がないのでは不可能である。
=> チームが今持っている知識を使って、アーキテクチャをざっくり設計して実装したもの。
ざっくり設計というのが重要。
ただ、適当ではいけない。システム要求を分析した上で深くなりすぎないよう設計するくらいのバランス。
なお、動くスケルトンは外部システムなども考慮した設計であること。
本番デプロイでも外部システムと連携した環境である想定。
そうしないと意味がない、システム開発の目標にならない。
デプロイ後に本番想定の1機能に対する受け入れテストなるものを作っておく
これがその機能開発の指針になる。
その受け入れテストがGreenになるまで開発をし続けることになる。
また、他機能を実装する際も既存の受け入れテストはGreenであることをチェックし続ける。
といった風な開発の進め方をする。
要するにポイント
システムが要件通りであるという検証を常に自動化で行う
システムが要件通りに動いていることを、常に自動テストで確認し続ける。人手に頼らない安心感
リスクを早期に低減する努力
デプロイに思わぬ障害がある
依存関係の見落としを避ける
などなど
後回しにせず、プロジェクト初期に潰しておく。
テストスイートによってゴールを常に明確にしておく
受け入れテストスイートが「何を作るべきか」を常に示してくれる。チーム全体で共有できる具体的な完成基準。
新しい機能を作る際は、先にその機能用の受け入れてストを作ると良い
そこで機能の仕様を表すテストケース一覧を作り出し、それがAll Greenになるまで実装を進める。
なお、このテストの記述はドメイン・ビジネスの言語で行うこと。技術的言語が入るのは良くない。
ビジネス要件としては、技術的要素など意に介さないから。
本書では、受け入れテスト = E2Eレベルのテストと記載されてる(俺の勘違いでなければ)onigiri.w2.icon
そして、受け入れテストをドメイン言語で書いておけば、実装基盤が変更されても影響がないとも記載されてる。
しかし、流石にE2Eレベルのテストなら、実装基盤が変わったらsetupなどに影響するはずなので、上記主張には違和感がある。
「ユースケーステスト」なら理解できるけど。
そこで、思ったのは、「ユースケース x ドメイン」のテストレベルも作っておきたいね。
そこがロジックの根幹になり、指針になると思う。
合わせて、受け入れテストでも同じテストを用意しておく。
ただし、受け入れテストの方は、実装基盤が変わったら影響すると思えばいい。
受け入れテストなどでは、「正常の普通のケース」を先に用意する
まどろっこしいのは無し。
普通のハッピーパスを用意しておくと良い。
テストタイトル・診断メッセージは「理解しやすい」「読みやすい」を意識する
テストケースを作った際には、テストタイトルを書く、かつ、失敗のメッセージが出る。
ここは常に読みやすく、理解しやすくすべし。
タイトルはテストの意図を表現できているか?を自問すべし。
じゃないと、失敗したテストを読んだ時の認知負荷が上がってしまう。
とにかく「外堀」から埋めていくこと
ゴール達成方法と同じ。
ボトムアップでやっても、無駄なことをしたり、間違った方向に進むだけ。
最終ゴールを明確にし、それを目指して実装していくのが一番効率が良い。
テストはオブジェクトの振る舞いを表すものである
振る舞いを示すためにテストケースがある。メソッドを中心に考えては意味がない。
そもそも、実装の前にテストケースは作られるべきである。それがゴールなので。
実装に引っ張られたテストケースは、実は論外。
単体テスト・インテグレーションテスト・受け入れテストのバランス
テストレベルごとの配分に、絶対的な正解はない。
受け入れテストに寄せすぎれば、ケースの組み合わせが膨れ上がり、重たいテストスイートになってしまう。
逆に、単体テストだけを厚くすると、統合したときに思わぬバグが潜むこともある。
だからこそ、開発と運用を続ける中で、自分たちのシステムにフィットするバランスを探し続ける姿勢が必要だ。
なお、一度「これが最適だ」と思えても、それで終わりではない。システムが成長し続ける限り、最適解は変わり続ける。
大切なのは、状況・コンテキストに合わせて、テストスイートを継続的に整えていくこと。
最初は、テストピラミッドやダイヤモンド型などの一般的な指針を参考にしつつ、自分たちなりのバランスを育てていけばいい。
あるオブジェクトの責務が大きすぎるか否かをテスト作成時に判断する
この判断軸は絶対というわけではない。参考まで。
対象オブジェクトのためのsetupコードが多くなるようでは、恐らくオブジェクトの責務が大きい可能性がある。
丁寧に責務を分解して、それぞれにオブジェクトを割り当てよう
「モックを使うことで、オブジェクト間のコミュニケーション(プロトコル)を明示的にできる」という意見
これは ロンドン学派 vs 古典学派 の対立を思い起こさせる話題。
onigiri.w2.icon は古典学派寄りなので、基本的にこのアプローチには反対である。
モックは対象オブジェクトの内部事情(呼び回数・呼び順・通信手順)にテストを結びつけやすい。
これらは外部仕様ではなく内部実装であり、テストのリファクタリング耐性を大きく下げる。
結果として、振る舞いの結果ではなく「実装手順」を監視するテストになりがちで、本質から外れる。
一方で、point.icon 通信そのものが“外部仕様としての成果物”になる場合(イベント発行・外部APIへの発話など)は、
プロトコル自体が振る舞いとなるため、その範囲に限ってモックは妥当であると考える。
つまり、内部でしか現れないコミュニケーションはモックするのは避ける
対して、外部仕様として見えるコミュニケーションはモックを使うのは妥当
と私は考えるonigiri.w2.icon
外部システムやAPIはそのまま使わずに、ポート&アダプタの考え方を使ってアダプタオブジェクトを使う 外部APIやシステムは、こちらでは制御できない。
また、想定通りの使い方を常にこちらができてるかも確定しきれない。
開発初期でも仕様通りに使えてるか不明だし、開発安定期でも向こうの意向によって仕様が変わることもあり得る。
「相手がこちらの認識通りに動きをしているのか」を常にチェックし続ける必要がある。
そこで、アダプタを使ってラップする。
ラップして、アダプタと外部システムを一緒にテストすることで、仕様通りの動作になってるかを自動テストすると良い。
外部システムは、意図通りに利用できているかを常に検証し続ける
学びを整理
hr.icon