テスト

自分のノートと『Google のソフトウェアエンジニアリング』第11〜14章を1本にまとめたもの。 書籍由来の主張は要約して (書籍11章) のように出典を付けた。本文の引用はしていない。

なぜ自動テストを書くか

根っこにある理由は1つで、変化できる状態を保つため。 テストは正しさの証明ではなく、変化の許可証として持つ。技術も市場も顧客の好みも変わるので、 素早く適応できること自体が製品の競争力になる。(書籍11章)

テストの価値は、エンジニアがそれをどれだけ信頼しているかで決まる。 信頼できないテストスイートは、テストがまったくない状態より害が大きい。(書籍11章)

そこから実務上の理由が導かれる。

目的何がなくなるか
テスト手順の記載と実施の時間を最小限にする全機能を毎回手動でテストすること。同じ手順を何度も書くこと
変化時のテストの時間を最小限にする大規模リファクタ・大規模設計変更のたびの手動テスト
仕様とユースケースを明文化する実装と食い違ったドキュメント。「どういうケースがあるか」の口伝
バグを早期に見つける緊急リリース、緊急リバート、本番で初めて気付くバグ
属人性を排除する人によって OK / NG が変わること
同じテストを共通化する同一内容のテスト手順の重複

副次的な効果として、テストを書く行為そのものが設計を教えてくれる。 テストしにくい新規コードは、たいてい担当機能が多すぎるか依存関係が扱いにくい。(書籍11章)

書きやすさは規模の問題でもある。何百万行のコード、何百のライブラリ、 言語 × 国 × デバイスの掛け算、そこにアクセシビリティとセキュリティが乗る。 これを手で回す前提は最初から成り立たない。(書籍11章)

効果はどこに出るか

  • 欠陥の大半がリポジトリへの提出前に捕まる。存続期間を通じて欠陥が減る
  • 依存関係側の変更で壊れたことがすぐ検出され、本番に出る前にロールバックできる
  • リファクタリングが促進される。 挙動を保つ変更なら、理想的には既存テストに一切手を入れなくていい
  • 1つの挙動だけを対象にした明確なテストは、実行可能なドキュメントとして機能する
  • レビュアーが各ケースを頭の中で追う必要が減り、テストが通っていることを確認すればよくなる

(以上 書籍11章)

逆に、信頼できないテストは持たない方がまし

脆いテスト — 期待結果を過剰に指定する、大きくて複雑な定型コードに依存する — は変化の妨げになる。 無関係な変更でも失敗するので、5行の修正で何十個ものテストが壊れる。 これが続くと、コードベースを健全に保つためのリファクタリングをチームが避けるようになる。(書籍11章)

脆さの最大の発生源はモックオブジェクトの誤用。 Google はモッキングフレームワークの誤用で相当な害を受け、 「モックは二度と使わない」と言い出すエンジニアが出るほどだったという。(書籍11章)

遅さも同じ問題を生む。エンジニアは遅いテストを待たないので、遅いほど実行されなくなる。 並列化や高速なハードウェアで底上げしても、遅い個別ケースが大量にあれば埋もれる。 sleep()setTimeout() による不要な待ちは、状態遷移の細かいポーリングとタイムアウトの組み合わせに置き換える。(書籍11章)

決定的でも高速でもないテストスイートは、いずれ必ず生産性の障害になる。 だから盤石なテストを用意したことにも、機能をローンチしたのと同じだけの評価を与える。 遅いテストや大して重要でないテストはリファクタリングする。 サポートするフレームワークとツールの数自体も絞る。(書籍11章)

テストスイートが健全なときとそうでないとき

同じ観点を両側から並べたもの。右に寄っているなら手を打つ。

重複

健全不健全
1つの挙動に対して自動テストは1つだけ過去に何回も同じテストを手動で行っている
バージョンアップ時は自動テストだけでよいバージョンアップのたびに膨大な手動テストが必要
テストデータが実際に即しているので自動テストだけでリリースできるテストデータが実データと違うので、ステージングでも本番でも同じ手動テストをやる
ブラウザ毎・言語毎の同じテストは毎回自動テストが行うブラウザ毎・言語毎に同じテストを手動で行っている
自動テストの信頼が低いので、自動・インテグレーション・手動と何層にも重複したテストが存在する

バグが見つかるフェーズ

健全不健全
開発中の自動テストで見つかるのでリバートされない自動テスト以降で見つかるのでよくリバートされる
プルリクにもリリースにもバグが入り込まないプルリクにもリリースにもバグが入り込む
本番リリース後に問題が発覚しない本番リリース後に問題があって初めて気付く
原因究明の手動デバッグが不要原因究明のために手動デバッグが必要
緊急リリースがない緊急リリースがよくある
どのリリースが原因かの切り分け作業が要らない切り分け作業が必要になることがある
リリースをプルリクと別管理しなくてよい / 手順がシンプル / すぐリリースできるリリースが別管理で高コスト / 手順が多い / 時間がかかる

自動テストの信頼性

健全不健全
変化を可能にする観点で設計されていて、プロダクトを変えやすいそう設計されておらず、変更の障壁になる
スケールと高速な変化に必要な存在。毎日何千ものリリース、毎週数千万行の変更が可能障害になる。そのどちらも不可能
挙動を変えないリファクタリングではテストが失敗しない。リファクタリングを促進する挙動を変えなくても大量に失敗する。リファクタリングの障壁
新機能追加・リファクタリング・大規模設計変更のときに間違いを捕捉できる捕捉できない
新規機能のテストを書く過程で設計の改善やケース不足に気付ける気付けない
生産性を上げる。費用対効果が高い生産性を下げる。ない方が費用対効果が高い
全部成功していれば実際の挙動は常に正しい。バグがあるときだけ失敗する全部成功しても挙動がおかしい。バグがなくても失敗する
バージョンアップ時に手動テストが不要で、素早く頻繁に上げられる手動テストが必要で、バージョンアップが遅れがち
最新の挙動と一致したドキュメントになるユースケースを網羅できておらずドキュメントにならない
テストコードレビューで挙動を担保できる担保できず、別途挙動を確認するレビューが要る

いいテストの条件

漏れがない

実際のユースケースやパターンを網羅している
すべてのパターンだと多すぎる場合は実際にあり得るケースを網羅できているか
レアパターンでも実際あり得るパターン
必須項目がすべてあるパターン
必須項目がすべてないパターン

各種バージョンアップをしたときに、テストの失敗を修正するだけで
手動テストが不要なくらい信頼性があるかどうか

通過や分岐としてのカバレッジ100%だけではなく、実際のユースケースやパターンを網羅している

網羅の基準はカバレッジではない。壊れてほしくないものを全部テストする、が基準。 挙動の正しさだけでなく、パフォーマンス・アクセシビリティ・セキュリティ、 そして障害時の振る舞いも対象に入る。一般的な障害を模擬する自動テストは書いておく。(書籍11章)

裏返しの厳しさもある。無関係なインフラ変更で自分たちの製品が壊れたとき、 自分のテストが全部通っていたのなら、壊れた箇所を直してテストを足すのは自分の側の仕事になる。(書籍11章)

カバレッジは行が呼ばれたことを測るだけで、その結果何が起きたかは測らない。 しかも他のメトリクス同様、すぐにそれ自体が目的化する。 テストされていない場所を知る手がかりにはなるが、 「十分にテストできているか」を批判的に考えることの代わりにはならない。代わりに問うべきなのは、(書籍11章)

顧客が動作を期待するものが全部動くという確信があるか
依存関係の破壊的変更を捕捉できる自信があるか
テストは安定していて信頼できるか

速い

実行が速い
関係しているコンポーネントのパフォーマンスが良いためsleepが不要
大量に存在する遅い個別テストケースが少ない
並列で実行ができる
DB関連のテストはトランザクション内でテストして、ロールバックされる
本物のオブジェクトでテストが遅い場合はパフォーマンス・メモリ溢れ等問題のある可能性が極めて高い

大きなテストを2つの小さなテストに割って並列に動かすのが、速度改善として最も効くことが多い。(書籍14章)

安定している(決定的である)

入力が同じ場合は常に同じ結果になる

テストは実行順など外部環境に前提を置かず、環境の構築・実行・解体に必要な情報を自分で持つ。(書籍11章)

挙動の変化と成功・失敗が対応している

挙動がおかしくなったときにちゃんと失敗する
レビュー時にソースを書き換えておかしな挙動にしてもちゃんと失敗する
大規模リファクタしても挙動が変わらなければテストコードを書き換える必要がない
APIのフレームワーク・言語・ライブラリが変わっても挙動が同じなら失敗しない
手動テストと同じ信頼性
自動テストだけで、手動テストは不要なくらいの信頼性

明確

なぜそのケースがあるのか明確で誤って削除されない
どういう場合なのか明確で再現方法が明確
失敗時のパラメーター、ユースケースが明確
失敗したケースだけを実行しやすい
結果は変数展開や動的に作成されていない

何がテストされているかのコメントが残されている
スタブデータの詳細がどのユースケースなのかコメントが残されている

contextのネストを深くしすぎない
異常系は先に単一のcontextにする
互いに影響する場合のみマトリックスのcontextにする
互いに影響しないものは単一のデータの各ケースをcontextにする

初めて見る壊れたテストを直す羽目になったとき、 読みやすく書いてあることのありがたみが分かる。コードは書かれるより読まれる回数の方がずっと多い。(書籍11章)

実装・レビューが楽

テストの実装が楽で速くできる
テストのレビューが楽
無理やり共通化をしていなく、修正しやすい
同じ内容の挙動のテストは共通化できている
テスト内で条件分岐やループを使っていない
テストのためのテストが必要ない
不必要にライブラリ・フレームワーク・言語の機能を使って複雑になっていないか

テスト自体にはテストがない。 だからテスト内の制御フロー(条件分岐・ループ)は強く非推奨。 複雑なフローはそれ自体がバグを抱えうるし、失敗原因の特定を難しくする。(書籍11章)

失敗時の依存先が明確

自チーム内の複数モジュールを含めたテストの場合にどの依存関係先が原因かすぐに分かる

現実に即している

公開されているものを使う側の視点でテストが書かれている
APIならそのAPIを使う側と同じアクセスの方法でテストされている
テストデータが実際のユースケースのデータ
個人情報はマスキングする
自動テストと実際に手動で動かすチェックの信頼度が同じかどうか

正常に動くソースのみコミットされる

コミット前にテストが実行される
テストが成功したもののみコミットできる

静的解析ツールが整備されている

テストフレームワーク用の静的解析ツールが整備済みであるか
設定ができるだけデフォルトであるかどうか
設定がバージョンアップを想定しているかどうか

何をテストするか

メソッドではなく挙動をテストする。実装詳細ではなく公開 API をテストする。 ここが崩れると、脆さのほとんどがここから生える。

理屈はこう。テスト対象システムのユーザーが呼ぶのと同じ方法で呼べば、 テストを壊す変更はユーザーも壊す変更ということになる。だからテストが現実的になり、脆さが減る。 おまけにそのテストは、ユーザーにとってのコード例とドキュメントを兼ねる。 逆に内部にテストが食い込んでいなければ、内部のリファクタリングは好きなだけやれる。(書籍12章)

メソッドと挙動は多対多で、複数メソッドの相互作用に依存する挙動もある。 だからメソッド単位ではなく挙動単位でテストを書き、1つのテストは1つの挙動だけを対象にする。(書籍12章)

見るのは結果が**何(what)**であるかであって、どうやって(how)そこに至ったかではない。(書籍12章)

テスト対象として妥当妥当でない
検証している挙動 / 壊れてほしくない挙動すべて実装詳細部分 / 実行されているコード
顧客が動作を期待するものすべて自分が書いたクラス
公開 API に対する呼び出しコードの内部構造についての前提条件
システムを使う側(顧客)の挙動・視点システムを実装した側の実装詳細 / そのクラスやメソッドを使うエンジニアの視点
挙動メソッド / どのメソッドを呼び出すか
実装から独立している実装に依存している
依存関係のクラスの破壊的変更の捕捉も対象破壊的変更の捕捉は対象外
1ケース1挙動1ケースに複数の挙動が混在
設定のバグを拾える設定のバグを拾えない

「範囲が狭い」というのは実行されるコードではなく検証されるコードが狭いという意味。 クラスが多数の依存を持ち、テスト中にそれらが呼ばれるのは普通のこと。(書籍11章)

サポートライブラリー(オーナーだけがアクセスできるが、一定の文脈で汎用的な機能を提供するもの)も ユニットとみなして直接テストする。使う側が減ったときにカバレッジの穴を開けないため。(書籍12章)

相互作用ではなく状態をテストする

DBに保存されることではなく、データを取得した結果が正しいかを見る

状態を見るテストの方がスケールする。脆さが減り、長期的にコードの変更と保守が楽になる。(書籍13章) ステートテストが書けない場合に限ってインテグレーションテストを書く。インタラクションテストに逃げない。

インタラクションテストを書かざるを得ない場合も、 どの関数がどの引数で呼ばれるかを過剰に指定しない。呼び方を変えただけで失敗するテストが減る。(書籍13章)

テスト対象とだぶり

ライブラリはテストが書かれている
他のクラス用のクラスのテストは書かず、使っているクラス側の挙動のテストに含める

テストの構造を実装の構造に合わせずに、挙動がテストされているか
実装とテストコードが互いに独立しているかどうか

エンジニア視点ではなく、ビジネス視点でテストが書かれているか

依存をどう扱うか

優先順位は 本物 > フェイク > スタブ。モックは使わない。

第一の選択肢は本物の実装。本番で動くのと同じ形で動かせば、テストの忠実性が上がり、 「ちゃんと動いている」という信頼が得られる。 テストダブルに寄りかかったユニットテストは、同じ信頼を得るために 結局インテグレーションテストか手動確認を追加させることになる。(書籍13章)

モックを使いすぎると、本物の実装と同期が取れなくなってリファクタリングを妨げるコードが増える。 モック化は、API のオーナーが長期的に実装を変える能力にも強い制約をかける。(書籍13章)

本物の実装は「遅すぎて使えなくなるまで」使い続けられることが多い。 テストで本物のクレジットカードサービスを叩くような、使うこと自体が非現実的なものだけが例外。(書籍13章)

何を置き換えるか

速度と決定性に影響があるもの(別サーバーや外部組織のDB等ネットワーク接続を確立)は使わずにスタブを使う
テスト対象クラスの多数の依存関係コードや他のクラスへの参照はテストダブルにはしない

スタブにするのは他チーム・他社の依存先だけ
自チーム内の外部依存先は実データにする
データはシードデータとしてリポジトリ管理されている

スタブの作り方

スタブデータは実データに即しているかどうか
スタブ化のソースはテストコードには書かずにライブラリで吸収する
スタブの内容が重複しないように結果は一時変数においてから各キーを比較する

時間や日付も現実に即したスタブにする
時間や日付のスタブが古くなりすぎたら最新にする
関係する箇所を修正する度に最新にする

他システムの変化とスタブは一致させる
スタブと実際のソースの切り替えが楽に切り替えれるかどうか
スタブは定期的に最新のものに更新できるのが楽かどうか

外部の複数のシステムに対する操作の順序の検証はモックではなく
postした後にgetしてその都度確かめる

スタブの危うさは3点。実装詳細がテストに漏れるので本番コードの内部が変わるとテストも直す羽目になる。 スタブが本物と同じように振る舞う保証がない。 そして保守するエンジニアの生産性を大きく削る。 スタブが妥当なのは、テスト対象をある状態に遷移させるために特定の戻り値やエラーが必要なときだけ。(書籍13章)

フェイクを使う場合は、本物の挙動が変わったら必ず追随させる。 一致していないフェイクを使ったテストは役に立たない。 公開インターフェイスに対してテストを書き、本物とフェイクの両方に同じテストを流すやり方がある。(書籍13章)

オブジェクト生成

オブジェクト生成機能はハードコードではなく、ファクトリーメソッドかDIで分離して、本番と同様のものを使う
オブジェクト生成がハードコードされておらず、コンストラクタが改修されたときにテストコードの保守が不要

テストの規模

範囲(ユニット / インテグレーション)では分けない。速度と決定性で分ける。 規模を決めるのは行数ではなく、そのテストが何をしてよいか、どれだけリソースを食うか。 そしてどの機能に対しても、書けるかぎり小さいテストを書く。(書籍11章)

規模許されること備考
ネットワークもディスクも触れない遅さと非決定性の主要因へのアクセスを断つ。該当する依存はテストダブルで軽量なプロセス内依存に置き換える
単一マシン内に収まる。DB インスタンスの実行、Web UI とサーバーコードの組み合わせWebDriver 等が必要になりやすい。柔軟だが非決定性が入るリスクがある
複数マシン、外部トラフィックE2E とテストダブルが使えないレガシー向け。開発者のワークフローを妨げないよう、ビルドとリリースの間だけで動かす

規模の定義を厳密に決めたことで、それを機械的に強制するツールが作れた。 小テストとタグ付けされたテストがネットワーク接続を張ろうとしたら、そのテストは落ちる。(書籍11章)

範囲と規模は独立している。テストダブルでプロセス外依存を全部代役させれば、 サーバーの構文解析・リクエスト検証・ビジネスロジックを全部通る広いテストでも小テストになる。 逆に、単一メソッドを対象にした狭いテストが中テストになることもある。(書籍11章)

良いテストスイートは、局所的な現実に即したテストから組織全体の現実に即したテストまでが混在している。 別チームが作った2つのシステム間の契約のような相互作用は、ユニットテストでは検証できない。(書籍11章)

なぜ大規模テストがいるのか

大規模テストは忠実性のために持つ。バグ捕捉の主要手段ではない。(書籍11章・14章)

ユニットテストだけでは埋まらない穴がある。(書籍14章)

  • データを手で作るので、扱う範囲が狭く、書いた人のバイアスに寄る
  • モック対象の実際の挙動を誤解していることがありうる。しかも本物が変わっても、 追随すべきだと知らせてくれるシグナルは存在しない
  • 書いた人の想像力の範囲を超えられない
  • 公開 API に明示されていない観察可能な挙動を、ユニットテストだけで押さえるのは難しい
  • 大量のトラフィックを流すパフォーマンス・負荷・ストレスのテストは、ユニットテストの型に収まらない
  • 製品の意図についての誤解は、コードとユニットテストの両方に同じように入り込む。 そうなるとテストは「意図通りに動いている」ではなく「意図通りに実装されている」ことしか確かめられない

巨大なテスト1つより、いくつかの大テストに分けた方がいい。 1つのテストの出力を永続化して次のテストの入力にする、という連鎖のさせ方がある。(書籍14章)

外部サービスについては、本物のサードパーティ API を自動テストから叩かせるのは非推奨。 代わりに、大規模テスト実行時に外部へのトラフィックを記録しておき、小規模テストで再生する手がある。(書籍14章)

テストデータは、質と量の両方で現実に即した一式を用意する。本番からコピーできることが多い。 必要最小限のデータで最大のカバレッジを狙う「スマートサンプリング」という考え方もある。 ただしテスト用データはエンドユーザーに発見されうる前提で扱う。(書籍14章)

A/B 差分テスト

異なる2環境での出力の差分が確認できる
リファクタ時の出力に変化がないことを確認
パフォーマンスの変化も確認できる

公開 API にトラフィックを流し、新旧バージョンのレスポンスを比較する。 コーナーケースの差異を捕まえられるだけのシナリオを含める必要がある。 人気がある理由は、検証ステップの保守にかかる人的コストが低いこと。 失敗時には、何を測っていて、なぜその挙動が怪しいのかが出力から分かるようにする。(書籍14章)

E2E テスト

コードがシンプルでコンパクトのため、E2Eはタグの読み込みによるタイムアウトやjsエラーが発生しない
タイムアウトの上限値はユーザーが待つであろう時間を超えない

本番のユーザーは待ち時間が短いことを望むので、テストも本番のユーザーと同じように反応するのがよい。 エンドユーザーが受忍できる挙動(最大許容タイムアウトは n 秒、など)を定義しつつ、 実行の不安定さも扱えるバランスを見つけるのが鍵。 アサーションは合否のシグナルを明確に出し、原因の切り分けに役立つエラー出力を返す。(書籍14章)

テストコードの書き方

テスト本体は、理解に必要な情報を全部含み、それ以外は何も含まない。(書籍12章)

良い悪い
重要ではない情報や紛らわしい情報を全く含まない含んでいる
理解するのに必要な情報を全部含んでいる含んでいない
ロジックが含まれていない含まれている
文字列が動的に作成されない動的に作成される
テストの共通コードがあるない

DRY より DAMP

共通化して無理にDRYにするよりは重複を許容して明確にする
テストが分かりやすくなるように共通化されていて、テストの意図はぱっと見で分かりやすいかどうか
重複と明確化のトレードオフのセンス

テストが説明的で分かりやすくなるなら、ある程度の重複は許容してよい。 ただし DAMP は DRY を置き換えるものではなく補うもので、 ヘルパーやテストインフラは今も有効。判断基準は 「説明的で分かりやすくなるか」であって「繰り返しが減るか」ではない。(書籍12章)

検証ヘルパーは、入力について単一の概念的事実が真であることを表明するものが最良。 テスト本体に置くと明確性を損なうループや条件分岐を、実装側に押し込みたいときに効く。 そしてテストインフラ自体は常に自分のテストを持つ。(書籍12章)

自動化しないもの

自動テストが向かない領域は残る。人間に任せる。(書籍11章)

検索結果の品質テスト(人間による判定が必要)
電話やビデオ通話システムのパフォーマンス評価(音声と映像品質の微妙な差異)
複雑なセキュリティ脆弱性の探索(人間の方が自動システムより上手)

人が欠陥を見つけて理解した後は、自動セキュリティテストに追加して継続的に探索させる。 こうした、あらかじめ何を探すか決まっていないテストは探索的テストと呼ばれる。 アプリケーションを解くべきパズルとして扱い、予期しない手順や予期しないデータを入れて、 見過ごされてきたコードパスや異常な応答を炙り出す。(書籍11章)

逆に、手動テストに回してはいけないもの。

改修の影響範囲のユースケース
自動テストでできる内容
同じテストを何回も
※エンジニアがちゃんと手動テストをしないとバグがすり抜け放題

関連

0件のページ