スポンサーリンク
概要
オライリーの「Googleのソフトウェアエンジニアリング 〜持続可能なプログラミングを支える技術、文化、プロセス〜」を読んだので感想を書こうと思います。
技術書のセールとおすすめ書籍を紹介しています。合わせてご覧ください。
スポンサーリンク
書籍概要
どんな本?
Googleの現役ソフトウェアエンジニアたちが、超大規模ソフトウェアの開発と保守を長期的に支えてきたGoogle社内の多様なベストプラクティスを、文化、プロセス、ツールの側面からこの一冊に凝縮。時間と変化、規模と成長、トレードオフとコストという3つの基本原理に沿って、コードを持続可能にする方法論を紐解きます。
「謙虚、尊敬、信頼」、心理的安全性、ダイバーシティとインクルージョンなど公正を重んじる文化から、コードレビューやテスト構成法など人間の行動を規定するプロセス、継続的インテグレーションや大規模変更システムなど変化への対応を支援する自動化ツールの基盤技術まで、Googleが試行錯誤を経て獲得した教訓を余すところなく紹介しています。
経済学、心理学、マネジメント論などを背景にした人間への深い洞察をふまえ、データ駆動かつトレードオフから導かれる、定量的かつ定性的な決定プロセスも解説。Googleの成長力の源泉を理解でき、得られる知見は、学生から組織の意思決定者、小規模スタートアップからデジタルトランスフォーメーション(DX)を目指す大企業まで、幅広く活用できます。
- PTitus Winters、Tom Manshreck、Hyrum Wright 編、竹辺 靖昭 監訳、久富木 隆一 訳
- 2021年11月 発行
- 684ページ
- 定価4,840円(税込)
スポンサーリンク
内容のまとめと感想
Google社内でソフトウェア開発時に実施されているプラクティスに関してまとめた書籍です。
Googleという超巨大なソフトウェア企業内で、どうやってうまくソフトウェア開発を行なっているのか、といった点を過去の経緯等を含めて細かく説明がされています。
文化、プロセス、ツールの観点で説明と書かれているように、非常に幅広い内容が取り上げられています。
良かった点
幅広いがディープ
こういった書籍の場合、考えや仕組みといった比較的に抽象的で広く浅く内容を取り上げがちになりますが、本書は684ページという圧倒的な物量で、幅広くかつ深く内容を説明しています。
文化や組織論といったような、比較的マネジメントやマインドセットに近い話から始まり、テスト手法、ツールなど幅広い内容を取り扱っていますが、技術的な話では具体的なコードを例にした説明が行われています。(主にJava)
説明されている仕組みや考え方などは、完全Googleオリジナルのものというのは少なく、「アジャイル開発(スクラム)」、「CI/CD」、「テスト」などの書籍を読んだ事があれば、馴染み深いものが多く出てくると思います。こういった専門書が一冊あるような分野を横断的に取り上げつつ、ある程度しっかりと説明がされている点は本書の凄さだと思います。
テストに関してかなり細かく書かれている
本書はユニットテストの書籍ではありませんが、ユニットテストを中心にテストのことに関してかなりのページを割いています。
ユニットテスト周りの話はコードを例に具体的で、かなり参考になりました。
過去にユニットテスト関連の書籍を何冊か読んできましたが、ユニットテストはどう書くべきかといった点で、本書以上ここまで深く書いてあるものには見たことがなかったです。
例えば、モックやスタブの使い方は書いてあっても、それをどこまで使うべきなのか?まで深く書いてあるものは本書が初めてでした。
(もちろんグーグルの場合という、具体的な前提があるからというのもあると思いますが。)
普段からユニットテスト関連でモヤモヤとしていた点などが、Googleでのプラクティスとそこに至った理由までが書かれていて非常に腑に落ちました。
ユニットテストをどうやって書くべきかといった指針を学びたいという人であれば読んでおいて損はないと思います。
章ごとのまとめがある
非常にボリュームが多い本書ですが、各章ごとに要約が書かれていて、内容を思い出したり理解するのを助けてくれます。
興味がない章などはまずはココだけを読んでしまうのも良いかも。
この辺りの丁寧さは、以前紹介した「ベタープログラマー」に通じるところがあります。
気になった点
Google固有のツールなどの説明はあまり役立たない?
特に後半ですが、Google社内で使われている独自のCI/CD、構成管理、コードレビューなどのツールに関する説明に多くが割かれています。
基本的に社内独自のツールなので、説明を聞いてもぴんと来ないのと、そのツールを使うに至った背景や思想などもGoogleという非常に規模が大きい特殊な組織に限られる内容なのではないかと関しました。
この辺りは人を選ぶのかもしれませんね。(少なくても私にはあまり刺さらなかったです。)
一部読みにくい
一部のセクションなどでは、翻訳の問題かちょっと理解が難しい箇所などが多く見受けられました。
こういった大ボリュームの書籍であると、翻訳の内容を隅々まで見直すのが難しかったのかもしれません。
総合的なまとめ
エンジニアにおけるマインドセットや組織論的な部分や、コードやツールなどのテクニカルな内容あったりと非常に幅広く深い内容を取り上げた書籍になっています。
Google社内のプラクティスという触れ込みですが、多くのものは汎用的でソフトウェアエンジニアならば読んでおいて損はないと思いました。
扱うジャンルが 広大で、ボリュームも膨大なので、自分が興味がある分野に注力して読むと良いかもしれません。
個人的にはテストの部分はここだけを切り出して、一冊にしても良いくらいの内容だと思いました。(コードを増やしてもっとテスト専門の本にして欲しい。)
組織論やツールなど、本書で取り上げられている内容に特化した書籍も多くあるので本書と合わせて読むとより理解が深まると思います。
何点か本ブログでも紹介しているので興味があれば、ご覧ください。
個人的なメモとまとめ(14章まで)
第1部 主題
本書に主題となる「ソフトウェアエンジニアリング」と「プログラミング」の違いの考えていく内容になっています。
「プログラミング」自体はコードを書く作業になるが、そのコードを保守するようにプログラミングを拡張していく作業を「ソフトウェアエンジニアリング」と述べられています。
製品を稼働させるために、有用にコードを保つために必要となるのがソフトウェアエンジニアリングで、プロセスやポリシー、プラクティスなどが含まれます。
そのソフトウェアエンジニアリングにおいて、有用となる知見を提供するのが本書というわけですね。
第2部 文化
この部では、Googleにおけるソフトウェアエンジニアリングの文化的、社会的な側面でのプラクティスをまとめた章です。
チームでの活動、知識共有、リーダー、生産性のメトリクスの出し方に関してGoogleでの事例を使って詳説がされています。
マネージメントに興味がある人には、この部が一番読みやすく、役に立つと思います。
知識共有に関してはGoogleにおけるドキュメンテーションの方法に関しても書かれていて、興味深いです。
リーダーの部分では、いかにスケール可能な自律的なチームを作っていくか、リーダー論が書かれています。このあたりは、スクラムマスターにおけるリーダーシップの技法(サーバントリーダー)などでも良く出てくる内容です。アンチパターンなども載っていて、非常に参考になります。
チームが全て(2.4)
Google社内においてもチームにおけるコミュニケーションが大切といった点が挙げられています。
「謙虚、尊敬、信頼」を忘れないようにしていきたいと思いました。
- ソフトウェアエンジニアリングとは、チームによる取り組みである。
- 優れたチームというのは、チームが擁するスーパースターを活用して全体で個人の総和で勝るものである。
- 孤独な職人のような天才プログラマーは幻想で、高度に機能したチームが成功の鍵となる。
- チームコミュニケーションとして必要なる社交スキルとして、「謙虚、尊敬、信頼」の3本柱が挙げられる。
学びを阻む課題(3.1)
組織全体で知識の共有を行う上で、障害となる点が説明されています。
最近は良く目にしますが、「心理的安全性」が挙げられていますね。
やはり、チーム内で間違いやリスクを取ることへの恐怖が蔓延してしまうと、積極的に共有して成長していくといった事が奪われてしまうのでこの点は大事だと感じました。
尊敬(3.7.1.1)
高位のレベルでは、ある程度の技術的リーダーシップが要求されるが、あらゆるリーダーシップが技術的問題を対象としているわけではない。
リーダーというものは、周囲の人々の資質を引き上げ、チームの心理的安全性を向上させ、チームワークと共同作業の文化を作り上げ、チーム内の緊張を緩和し、Googleの文化と価値観の規範を打ち立て、よりかっきと刺激に満ちた仕事場にするのである。嫌な奴は良きリーダーではない。
(Googleのリーダーシップ)
この辺りはスクラムにおけるあるべきリーダー論やファシリテーション技術として出てくる内容で、腑に落ちました。
Googleでも求められるリーダーシップというのは同じなのですね。
マネージャーにおけるアンチパターン(5.4)
- 推しに弱い者を採用する
- Yesマンばかりになり、仕事の移譲ができない。
- 自分よりも賢くて交代できるものを採用し、仕事を移譲できるようにする。
- 成績の悪い者を無視する
- 成績の悪い者が改善、去らない間にチーム全体のパフォーマンスに影響を与える。
- 学ぶ過程を伴奏し、手伝う(一時的なマイクロマネジメント。ただし、謙虚、尊敬、信頼を忘れてはならない。)
- 特定の期間に具体的なゴールを設定し、成功か失敗かを測れるようにマイルストーンを決める
- 人間的問題を無視する
- プライベートな問題等をちゃんと考慮する
- 全員の友人になる
- 無理に全員と友人になる必要はない。友人でなくてもチームを率いて合意形成はできる。
- 採用基準で妥協する
- 5人採用する必要があるからと、全応募者から5人を必ず選ぶのは良くない。(不必要となった人材を解雇するのは採用よりも大きいコストが必要)
- 自分のチームを子供のように扱う
- マイクロマネジメントはすべきではない。信頼できる人材を採用、育成すべき。
第3部 プロセス
この部では、ソフトウェアエンジニアリングにおいて実施される各種プロセスのプラクティスに関してまとめられています。
コーディングルールや、コードレビュー、ドキュメンテーション、テストといった一番技術的な要素が多く含まれており、コード例なども出てきたりするため、プログラマに一番馴染み深い部になっています。
コーディングルールでは、ルールを設定する背景から始まり、設定すべき内容が書かれていて興味深いです。やみくもにルールに従うのではなく、その背景をしっかりと理解するのが大切ですね。
テストに関しては4つも章を割いていて、ユニットテスト〜インテグレーションテスト〜e2eテストまで全てをカバーしており、かなり力が入っています。
(コーディング)ルールを作る(8.2)
ルールのセットを定義する場合、鍵となる質問は「どんなルールを備えるべきか」ではない。
尋ねるべき質問は、「どんなゴールを前進させようとしているか」だ。
〜〜
「スタイルガイドに何が入るか」といった質問はせず、むしろ「何故それがスタイルガイドに入るのか」と質問する。
コードレビューのベストプラクティス(9.4)
- 礼儀正しく、プロフェッショナルになれ
- コード作成者のアプローチを尊重し、好みの問題ならば受け入れる
- 見つけた欠陥に関して、何故そのようなアプローチがとられたのかを質問した方が良い
- 小さな変更を書け
- レビュー対象は200行以下の小さい変更にすべき。(負担小、素早いフィードバック)
- 良い変更説明を書け
- 何故変更されたのかを詳細を書く
- レビュアーの人数は最小限にとどめよ
- 最も重要なレビューは最初のもので、継続されるものは効果が減る(コストがメリットを上回る)
- 必要ならば、別々の側面でみてもらうようにする
- 可能な場合は自動化せよ
- フォーマッタや静的解析等で事前でコードの自動チェックを行う
テスト規模(11.2.1)
Googleではテストを小、中、大の3つに分類する。
分類の要因は、速度と決定性(常に同じ結果になるか)となる。基本的な考え方はよく見かけるテスト分類と同じである。
- 小テスト
- ユニットテスト
- 単一プロセスで実行される
- IO操作等は許さない
- 中テスト
- インテグレーションテスト
- 単一マシンで実行される
- マルチスレッド、マルチプロセス
- 他のマシンへのアクセス(API)は許さない
- 大テスト
- e2eテストやシステムテスト
- 任意の好きな場所で実行される(複数マシン、プロセス等)
コードカバレッジについてのメモ(11.2.4)
コードカバレッジはしばしばテスト品質を理解するための基準になりがちだが、実行された行に対して何を確認しているのか不明なのでアンチパターンである。
またカバレッジ自体がゴールとなってしまって、それ以上のことをしなくなりがち。(80%が指標ならば大概のエンジニアは80%を超えるくらいでコードを提出する)
一番大切なのは、テストされる挙動を考慮したかである。
- 顧客が期待する全ての動作が動く確信はあるか?
- 依存関係における破壊的変更を捕捉できる確信はあるか?
- テストは安定しており、信頼できるものか?
この辺りの話は、多くのテスト関係に本にも書かれているがGoogleにおいても普遍的な話なのだと安心しました。
変化しないテストを目指す(12.2.1)
理想のテストとは変化しないテストである。(要件が変化しない限り2度と変更が必要ない。)
これが満たせないと、修正のたびに古いテストコードの保守が必要となるため、本来の価値ある作業に取り組めなくなってしまう。
下記の4種類の変更に対して、テストコードがどうあるべきなのかを示されている。
- 純粋なリファクタリング
- 変更が必要となる場合、適切な抽象化レベルでテストが書かれていない(振る舞いに対して正しいテストがされていない)
- 新機能
- 既存のテストに影響があるならば、意図しない結果を生んだか、テスト自体が不適切
- バグ修正
- テストケースが抜けていたという事を意味する。追加するケースは既存テストへの変更を含むべきではない。
- 挙動の変更
- 既存テストの更新が必要になる可能性が高く、変更にコストが高くつく。少数の新しいテストを追加するだけで良い形にすべき。
公開API経由のテスト(12.2.2)
ユニットテスト関係でよく話題になるテスト対象の話が書かれています。
publicインタフェースからテストすべきというのはよく出てくる話と同じですが、ただpublicからテストするのではなく、どういった単位でテストすべきか書かれているのが参考になります。
要件が変化しない限り、テストが変化することが必要ないことを保証するためのプラクティスとして最も効果が高いのは、「ユーザと同じ方法でシステムを呼び出すテストを書くこと」。
実装の内部的な詳細部分ではなく、公開APIに対してテストを行うことである。
- こういったテストが破綻する場合、ユーザの仕様も破綻させる(異常を検知できる)
- 有用なコード例やドキュメンテーションになる
- 内部的な些細な変更で影響を受けない(リファクタリング可能)
公開APIの定義は、一般的なプログラミング言語におけるpublicなどの修飾子と一致するわけではない。
では、そのスコープをどうやって定義するかのプラクティスは下記となる。
- メソッド、クラスが別の1〜2つの別のクラスを支援するためだけに存在する(ヘルパークラス)場合、それ自体を単体でテストすべきではない。使用するクラス経由でテストすべき。
- パッケージやクラスがオーナーに相談することなくアクセス可能なように設計されているものはテスト対象になる
- パッケージやクラスが内部的に使用されるものであっても、一般的な機能を提供する場合はテストすべきである
- その場合、使用する側のテストと冗長が生じるが、有益となる場合が多い
相互作用ではなく、状態をテストせよ(12.2.3)
ユニットテストでのチェックすべき内容に書かれています。
これもよく出てくる振る舞いではなく状態をチェックすべしという話ですが、そこよりさらに踏み込んでモックオブジェクトの多様による弊害を取り上げています。
確かにモックオブジェクトを多用していると、何をテストしているのかわからなくなってしまいがちという点でここの内容は腑に落ちました。
メソッドを実行した結果、システムがどうなるのか?(状態)をチェックすべきであり、インタラクション(相互作用)をチェックすると脆いテストになる。
- インタラクション:(内部にある)XXXというメソッドを呼び出したということチェックする
- 状態:XXXというメソッドを呼び出した結果、YYYという状態になったということをチェックする
モックの多用は、脆いインタラクションテストを生み出す。なのでモックオブジェクトより本物のオブジェクトを使いことをGoogleは好む。
明確なテストを書く(12.3)
テストが失敗するときに考えられる理由は下記の2つ
- テスト対象システムに問題や欠陥がある
- テスト自体に欠陥がある(脆かったり、仕様が間違っている)
テストの失敗時にその原因を究明する速度は、テストの明確性に依存している。
- テストの存在理由と失敗理由が明確であれば、失敗の原因を見つけるのは容易である
- 失敗の理由が明確でなかったり、何故書かれたテストなのか理解できないものは明確性が無くNG
テストは完全かつ簡潔にせよ(12.3.1)
テストコード本体がその結果に到達すべき全情報を全て含んでいれば、そのテストは完全である。
テストコード本体に無関係な情報が含まれていなければ、簡潔である。
例:足し算の計算を行うテスト(2+3 = 5といった計算)
- 悪い例1:テストに関係ないパラメータを含んでいる。(足し算と関係ないそのクラスの情報)
- 良い例:テスト本体に関係ないパラメータはヘルパーメソッドなどでテスト本体から切り離す
- 悪い例2:テストの結果の意図がわからない(ヘルパーメソッド側でパラメータを設定してい、いきなり5という結果をアサートしている。なぜ5になるのか不明。)
- 良い例:パラメータである1と2という値はテスト本体にある
メソッドではなく、挙動をテストせよ(12.3.2)
メソッドに対応するテストを書いてしまうと、最初は便利だが、時間の経過とともに問題になる。
改修されてテストされるメソッドが複雑になると、テスト自体も複雑になる。結果として何をテストしているのか理解しにくくなる。
例:Aというメソッドの仕様
- 購入した商品名を表示する
- 残高が足りていない場合、加えて警告を表示する
悪い例:1つのテストメソッド内で、1:商品名が表示されること、2:残高エラー の2つをアサートする
良い例:2つのテストメソッドに分ける。1:商品名が表示されることのテスト。2:残高エラーとなることのテスト。
テストにロジックを入れるな(12.3.3)
テストコード自体に演算やループ、条件分岐を入れるべきではない。
ちょっとした重複などはコードの理解やバグの発見を妨げるのに比べれば大した問題ではない。
悪い例:baseUrl(https://xxx)という引数でURLを定義し、それをテスト対象に渡す。期待値のアサーションもbaseUrl+aaaといった形でアサートする
テストとコード共有:DRYではなくDAMP(12.4)
テストコードにおいて、DRY(コードの共有化)よりもDAMP(説明的かつ意味が分かり易い)が優先されるべきである。
テスト間で共通的なメソッドを切り出してしまうとかえってテストコードの理解を妨げてしまう。
(テストにおいて重要ではない処理をDRYとして共通化するのは良い)
多少の重複よりも単純さや明確さの方が重要である。
共有値(12.4.1)
テストにおける共有値の良い例と悪い例
悪い例;テストメソッドのスコープ外に曖昧な共有の変数を定義する(例:Account1,Account2...といった様々なケースの変数)
- それがどこで使われるのかわかりにくい。テストコード本体から見てもその変数を見つけるのにスクロールが必要
良い例:パラメータ付きのヘルパーメソッドによる共有
- ヘルパーメソッドに明示的にそのテストに必要な情報を渡す(例:CreateAccount(xxx)というメソッドで目的に応じたアカウントを作れる)
テストダブルの、ソフトウェア開発への影響(13.1)
テストダブル(モックとスタブ)はテストのスコープを減らすことで、本来は大〜中テストになるべき事項を小テストとして実行可能とするメリットがある。
しかしながら、利用に対してのトレードオフがある。
- テスト可能性
- テストダブルを利用可能な形で設計されていないと、利用可能にするためのリファクタリングに相当大きいコミットメントが求められる
- 応用性
- 不適切に使用すると、テストが脆く、複雑で効果の劣るものになってしまう
- 忠実性
- 本物の実装と同じ動きをしていないと効果は無い
Googleは過去の経緯から現実的なテストを書く方を優先し、極力モックの利用は避けている。
- モックを多用した結果、テストを書くのは簡単になったが、バクを滅多に発見しない代わりに保守のための労力が必要だった
テストダブルの利用のためのテクニック(13.4)
フェイキングとスタビングの違いに関して、これまで違いを意識していなかったので参考になった。
- フェイキング
- 本番環境では使えないが、実際と同じ動きをするもの。DBの代わりにテスト時に使用するインメモリDBとか。
- スタビング
- テストコードを書く側が任意の戻り値を返すようにできるもの。
- インタラクションテスト(モッキング)
- 依存するの関数を実際に呼び出さずに、引数などの検証を行うもの。
- 使いすぎると脆いテストになりやすい。
分離より現実に即することを優先せよ(13.5.1)
テスト内での依存関係について、本物の実装を用いることで、現実に即したものになる。
テスト対象システムが正常に動作していることをテストダブルを使用した場合よりも、より多くの信頼をもたらす。
テストダブルを多用すると、インテグレーションテストや手動でのテストが必要となり、余分なタスクが増加する。
いつ本物の実装をつかうべきか決める方法(13.5.2)
下記の点を考慮して、本物かテストダブルを使用するかを判断する
- 実行時間
- 本物の実装の実行時間がかかるならば、テストダブルが有効
- 決定性
- 常に同じ結果にならない場合。(外部とのサーバと通信するとか)
フェイキング(13.6)
なぜフェイクが重要なのか?
=> 実行は高速で、本物の実装と同じ動きをする。
ただし、作成に比較的多くの労力と経験を要する。本物の挙動が変わったらメンテしないといけない。
なので、本物の実装者がフェイクもメンテすべきである。
どんなときにフェイクが書かれるべきか?
=> トレードオフが重要。一握りの利用者しかいないならば、労力に見合わないかもしれない。
少ない保守でフェイクを利用するには?
=> テスト利用が現実的では無い箇所の最上位の部分で実装する。(例:DBアクセスを行う各クラスではなく、DBアクセスを行うクラス自体をフェイクにする。)
フェイク自体もテストがされるべきである。
=>本物の実装と同じ動きを行うかテストする。(契約テスト)。実行時間コストがあるので、オーナーのみがやるべき。
スタビング(13.7)
本物の実装を使うのが手軽で無い場合にスタブは便利だが、使いすぎると保守の生産性が大きく落ちる可能性がある。
- テストが不明確になる
- 挙動を定義する余分なコードが増えて、テストの意図の明確さが薄れる
- そのコードに馴染みの無いエンジニアが理解するのを妨げてしまう
- テストが脆くなる
- 実装元の実装詳細が変化したら、その変更を反映しないといけなくなる
- 優れたテストはAPI挙動の変化の時のみテストの変更が必要なもので、それに反する
- テストの効果が落ちる
- スタブの動きが実際の実装と同じであると保証するものがない
スタビングが有効な手段になるのは?
テスト対象をある状態に遷移させるのに、特定の関数が値を返さなければいけない場合。
様々なエラーケースなどフェイクや本物では起こすのが難しいケースで、アサーションの内容と直接関係あるものだけとすべき。
インタラクションテスト(13.8)
モックフレームワークのおかげでインタラクションテストは容易に実施可能になってきた。ただし、変化に強いテストにするにはインタラクションテストを必要な場合のみ実施するのが大切。
インタラクションテストよりもステートテストを優先せよ
ステートテスト:対象の処理を呼び出して、正しい値が帰ってきたか?または何らかの状態が変化したかを確認する。
例:ソート機能のテスト(アルゴリズムがDIできるような構造)
ステートテスト:モックしていない本物のアルゴリズムを渡して、実際に実行結果が想定されたソート結果かを確認する
インタラクションテスト:モックしたアルゴリズムを渡して、モッククラスのメソッドが想定通り呼ばれたかを確認する
インタラクションテストでは、モックしたクラスが想定通り呼ばれたこと(ソートを試みたこと)を確認できるだけで、システムが正しく動いていることを知らせることができない。
また、実装詳細をテストしているため、スタビングと同様に脆いテストになる。
インタラクションテストが適切なのはどのような場合?
- 本物の実装が使えない(処理速度が遅い、フェイクが無い)場合に次善策として利用する
- 関数の呼び出し回数や順序の違いが望ましくない挙動を引き起こす場合にアサートしたい時(キャッシュによる呼び出し回数の減少チェックとか)
インタラクションテストはステートテストを置き換えられるものでは無い。
ステートテストを実施できないなら、インテクグレーションテストなど、より広範囲なテストによってカバーするのが望ましい。
(例;DB周りの処理をインタラクションテストで実施するならば、インテグレーションテストで実際にDBにアクセスするステートテストを実施する。)
インタラクションテストのベストプラクティス
- 状態変更型関数向けの場合のみインタラクションテストの実施を選べ
- 状態変更型:テスト対象の外に副作用がある関数。(SendMail、SaveRecordとか)
- 別の場所に対して、状態変更を試みていることをチェックできるので有用
- 非状態変更型:副作用がない関数。(GetUser, FindResultsとか)
- 相互作用のパターンが変化すると常にテスト更新が必要となり脆くなる
- 不必要なアサーションが増えて、テストの読みやすさが低下する
- 状態変更型:テスト対象の外に副作用がある関数。(SendMail、SaveRecordとか)
- 過剰な指定は避けよ
- 複数の挙動をチェックするのではなく、メソッドまたはクラス内の1つの動作を検証すべき
- テストの独立性が上がり、挙動の変化による失敗に強くなる
- 例:過剰な指定
- 悪い例:1つのテストで、1:ユーザーログイン情報の確認(ログインによる変化)、2:朝のあいさつとアイコンの確認(時間帯による変化)を行う
- 良い例:2つのテストに分ける
- ユーザログイン情報の確認
- 朝の時間帯における朝のあいさつとアイコンの確認
- 複数の挙動をチェックするのではなく、メソッドまたはクラス内の1つの動作を検証すべき
大規模テストとは何か?(14.1)
大規模テストはいくつかの本物の依存関係と、比較的少数のテストダブルを使う。(一般的にe2eテスト、システムテストと呼ばれる)
下記のような特性を持つ
- 遅いかもしれない(Googleでは15分か1時間のデフォルトタイムアウト)
- 密閉されていないかもしれない(他のテストやトラフィックとリソース共有している可能性)
- 非決定性かもしれない(他のテストやユーザー状態が干渉するかもしれない)
このような不確定性を持ちならがらも実施するのは、システム全体が意図通りに動作していることについての一層の信頼を提供できるから。自動化する事でスケールもできる。
ユニットテストでよくある不足部分
大規模テストでは、ユニットテストでカバーできていない下記の点をカバーする点で有用である。
- 忠実ではないテストダブル:大概はモックを作るエンジニアとモック対象を作ったエンジニアは別なので、挙動に対して誤解が生じる。
- 設定の問題:ユニットテストでは本番用の設定ファイルや設定DBの互換性の検証はできない。そういった設定の問題を検証できる。
- 負荷がかかるとおこる問題:ユニットテストでは再現が難しい
- 予期しない挙動、入力、副作用:ユニットテストでは予期できない挙動が実際には製品で発生する
- 不意に起こる挙動と「真空効果」:ユニットテストは意図的に外部影響を受けないようにしているため、想定外の挙動に気がつけない
大規模テストの類型(14.4)
- 1つ以上のバイナリの機能テスト:機能テストのシナリオ
- ブラウザーとデバイスのテスト
- パフォーマンス、負荷、ストレステスト
- デプロイ設定のテスト:設定ファイルなどのインテグレーションのテスト
- 探索的テスト:訓練されたユーザー、テスターによる経験則的なテスト
- A/Bテスト:新旧バージョンを同時に実行
- ユーザー受け入れテスト:CucumberやRSpecといった振る舞い駆動開発によって自動化できる
- ブローバーとカナリア分析:本番環境の問題の早期検知。ブローバー:スモークテスト的なもの
- 障害復旧とカオスエンジニアリング:システムに欠陥を継続的に送りこんで何が起こるかを観察する