『継続的デリバリー』読書会第4章
テスト戦略を実装する
大崎的デリバリー@favril
4.1 導入 (1/5)
• 「高品質を実現するために、大人数での調査に頼るのをやめよ。まずはプロセスを改善し、本番の品質を作り込め。」
• テストは– 職務横断的な活動
– プロジェクト初期から継続的に行う必要がある• デプロイメントパイプラインの一部としてテストを実行し、アプリケーション、設定、環境などに変更があるたびに実行されるようにする
– テストが通る=顧客が求める機能が完全に正しく実装されたことの証明になる
4.1 導入 (2/5)
• 品質を作り込むとは
– 自動テスト戦略を改善するために定期的に作業するということ
– 自動テストをさまざまな抽象度で書く
• ユニットテスト、コンポーネントテスト、受け入れテスト
– 手動テストを継続的に実施
• ショーケース、ユーザビリティテスト、探索的テスト
4.1 導入 (3/5)
• 機能面だけでなく、キャパシティやセキュリティなどの非機能要件も、それを守らせるためのテストを書く
– それらの要件を破る問題を早期に検知可能
• 発見が早ければ、修正コストも安くすむ
4.1 導入 (4/5)
• 既存プロジェクトに、理想的なテスト戦略を導入するのは簡単ではない– 自動テストのカバレッジを高めるのに時間がかかる
– 自動テストに関してチームが学習している間も、開発が続けられるようにする必要がある
– 最初から自動テストを導入して構築されたシステムと同じレベルの品質に至るまでには長い時間がかかる
– レガシーシステムに適用する方法は後半で
4.1 導入 (5/5)
• テスト戦略の設計とは
– プロジェクトのリスクを識別して優先順位をつけ、それを緩和するためにどんなアクションを取ればよいか決定するプロセス
• さらに– ソフトウェアが期待どおりに動くという自信
• バグが減り、サポートのコストも安くあがり、製品の評判もよくなる
– 開発プロセスに制約• 優れた開発プラクティスが促進される
– 最も完全で最新化された形式のドキュメント• システムがどう動くべきかだけでなく、実際にどう動くかも書いてある
4.2 テストの種類
自動
機能の受け入れテスト
手作業
ショーケースユーザビリティテスト
探索的テスト
ユニットテスト
インテグレーションテスト
システムテスト
自動
非機能の受け入れテスト
手作業/自動
ビジネス視点
技術視点
プロジェクト評価
プログラミング支援
4.2.1 開発プロセスをサポートするビジネス視点のテスト (1/3)
• 受け入れテスト– ストーリーに対する受け入れ基準が満たされていることを保証するテスト
– 開発前に書かれていて、自動化されているのが望ましい
– 疑似本番環境で実行すべき• ただし、外部サービス連携部分に対してはモックを使うかも
– アジャイルには欠かせない• 開発者の「何ができれば実装完了か?」と、ユーザの「必要なものは手に入ったか?」という問いに同時に答える
– 理想的には、顧客やユーザが受け入れテストを書くのが良い 機能の受け入れテスト
ショーケースユーザビリティテスト
探索的テスト
ユニットテスト
インテグレーションテスト
システムテスト
非機能の受け入れテスト
4.2.1 開発プロセスをサポートするビジネス視点のテスト (2/3)
• 受け入れテストツール
– Cucumber, Jbehave, Concordion, Twist
– テストスクリプトを実装と切り離しつつ、シンプルに実装と同期できる仕組みを提供
4.2.1 開発プロセスをサポートするビジネス視点のテスト (3/3)
• 正常パス– あるストーリーや要件における、正常動作の流れ
– Given-When-Then モデルで表現されることが多い• ○○な状態が、ユーザが△△することで、××になる
• 代替パス
– 初期状態や、実行されるアクション、最終的な状態にはバリエーションが存在する場合が多く、別のユースケースになることがある
• 異常パス– 正常でも代替でもないエラーになるべき流れ
• 最も適切なテストケースを拾い出すには直感が必要
4.2.2 受け入れテストを自動化する(1/4)
• 自動受け入れテストの価値– フィードバックループを加速させる
– テスターの作業負荷を軽減する• 探索的テストやもっと価値の高い活動ができる
– 強力なリグレッションテストスイートになる• 大規模アプリ、大規模チームの際に重要
– 要件ドキュメントが自動生成可能になる• ドキュメントが陳腐化しない
機能の受け入れテストショーケース
ユーザビリティテスト探索的テスト
ユニットテスト
インテグレーションテスト
システムテスト
非機能の受け入れテスト
4.2.2 受け入れテストを自動化する(2/4)
• リグレッションテストは特に重要
– 四象限のカテゴリをまたがる
– 自動テストが全体のリグレッションテストになる
– 変更をした際に、既存機能を壊していないことを保証する
– リファクタリングも安心して行える
– 優れたプラクティス&適切なツールの使用で、恩恵がコストを明らかに上回る
• 詳しいテクニックは8章で
4.2.2 受け入れテストを自動化する(3/4)
• すべてを自動化する必要はない
– ユーザビリティやルックアンドフィールの一貫性、探索的テストなどは自動化するのが難しい
– 多くの場合、手動テストで十分だし、手動テストの方が自動テストより優れている
– 自動テストでは正常パス的ふるまいを網羅し、それ以外は一部だけ• 安全だし効率的
• ただし、他の種類の自動リグレッションテストで包括的なものが一式揃っていることを前提とする
• 包括的=カバレッジ80%以上– とはいえ品質が重要で、カバレッジは貧弱な尺度
– 単体/統合/受け入れテストが含まれるていて、それぞれが80%以上
4.2.2 受け入れテストを自動化する(4/4)
• いつテストを自動化すべきか– 経験則としては、同じテストを2回繰り返したら
– ただし、テストの保守に大量の時間を費やさなくてもよいと自信のあるとき
• どこを自動化すべきか– 主要な正常パスに対するテスト
• 開発者のスモークテストとして使われる– 作業中の機能の一部を壊してないか、素早くフィードバックを提供すべき
– さらに• アプリが安定してるなら、代替正常パス• バグが多いなら、異常パス
受け入れテストはUIを叩くべきか?
• 理想的にはアプリのUIに対して直接実行すべき
• だがUIテストツールは、UIとテストを密結合させる– 誤った判定が多く下される
• アプリにはまったく問題がなくても、チェックボックスの名前が変わっただけでテストが壊れる
– テストをアプリと同期させておくために、大量の時間を浪費する
• 解決策– 1、テストとUIの間に抽象レイヤを設ける
– 2、UIのすぐ下に公開APIを設け、それに対してテストする
– 詳細は8章
4.2.3 開発プロセスをサポートする技術視点のテスト (1/2)
• ユニットテスト– コードの特定の一部を個別にテストする– DBにアクセスしたり、ファイルシステムを使ったり、外部システムと通信したりしてはならない
– 非常に高速に実行でき、変更によって既存の機能が壊れていないか素早くフィードバックを受けれる
– システム内のほぼすべてのパスを網羅する必要あり(最低でも80%)
– アプリの様々な構成要素間でやりとりが行われた結果発生するバグは取りこぼす
機能の受け入れテストショーケース
ユーザビリティテスト探索的テスト
非機能の受け入れテスト
ユニットテスト
インテグレーションテスト
システムテスト
4.2.3 開発プロセスをサポートする技術視点のテスト (2/2)
• コンポーネントテスト(インテグレーションテスト)– ユニットテストより大きな機能のクラスタをテストする
– ユニットテストが取りこぼす問題を検出可能– ユニットテストよりは遅い
• セットアップに時間がかかる• I/Oが多い• DBやファイルシステム、他システムとも通信する
• デプロイメントテスト– デプロイメント(インストール/設定/他サービスとの接続など)がうまくいったことを確認する
4.2.4 プロジェクトの評価をするビジネス視点のテスト (1/4)
• 期待されている価値を、アプリがユーザに実際にデリバリーしているかを検証するテスト– アプリが仕様を満たしていることを検証するだけでなく、仕様がそもそも正しいのかを確かめる
– 仕様が前もって完璧に定義されたプロジェクトは存在しない
– ソフトウェア開発は本質的にイテレーティブなプロセスで、効果的なフィードバックループを構築して初めてうまくいく
機能の受け入れテスト
非機能の受け入れテスト
ユニットテスト
インテグレーションテスト
システムテスト
ショーケースユーザビリティテスト
探索的テスト
4.2.4 プロジェクトの評価をするビジネス視点のテスト (2/4)
• ショーケース
– ビジネス視点のプロジェクト評価用テストで最も重要
– アジャイルでは、イテレーションが終わるごとにユーザに実施し、新しい機能をデモする
• 誤解や、仕様に問題があっても早期に検知できる
– 良い面/悪い面
• 良い面:ユーザ(顧客)が新しい成果を手にし、いろいろ試せる
• 悪い面:その結果として、大量の提案や改善
4.2.4 プロジェクトの評価をするビジネス視点のテスト (3/4)
• 探索的テスト
– テスト実施時、テスト設計を積極的にコントロールし、得られた情報を使ってよりよいテストを新しく設計する
– 単にバグを発見するだけでなく、自動テストを新しく作ることにもつながる
– 潜在的には、アプリに対する新しい要件のための素材も提供する
4.2.4 プロジェクトの評価をするビジネス視点のテスト (4/4)
• ユーザビリティテスト– アプリが実際にユーザに価値を提供できるかを測る究極のテスト• ユーザがソフトウェアを使って目的を達成するのが、どれほど簡単かを知るために行う
– 開発をしていると、問題に視野が限定されてしまうことは(技術畑でない人であっても)よくある
– アプローチの方法や、集積するメトリクスは様々– カナリアリリース
• 実際のユーザに、ベータテストさせる
• 少しだけ異なるバージョンのアプリを同時に本番に置き、特定のユーザにだけ使用させて効果を比較する
• 十分に価値をデリバリーしてなければ、その機能を破棄する• 非常に効果の高い機能を採用できる
4.2.5 プロジェクトの評価をする技術視点のテスト
• 非機能テスト– 機能以外のシステムの品質をすべてテストする
• キャパシティや可用性、セキュリティなど
– 非機能の受け入れ基準はアプリの要件の一部として定義されるべき
– テストやそのツールは、機能テストのものと異なるものになりがち• 実行に特殊な環境が必要だったり、準備・実装に専門知識が必要だったり
• 実行に長い時間がかかる
– 最近はテストツールが成熟してきているので、プロジェクト開始時に、少なくとも基本的な非機能要件のテストはいくつか準備しておくことをお勧めする
機能の受け入れテスト
ユニットテスト
インテグレーションテスト
システムテスト
ショーケースユーザビリティテスト
探索的テスト
非機能の受け入れテスト
4.2.6 テストダブル
• テストダブル(モックやスタブ、ダミー)のタイプ– ダミーオブジェクト
• 実際に使われることはなく、パラメタリストを埋めるためだけに使われる
– フェイクオブジェクト• 実際に動くが、手抜きをしている
– スタブ• テスト中に行われる呼び出しに対し、お決まりの回答を返す
– スパイ• スタブの一種で、どう呼ばれたかに関する情報をある程度記録する
– モック• 呼び出されるであろう内容をあらかじめ定義しておき、期待してない呼び出しには例外をスローし、期待される呼び出しがすべて行われたことをチェックできる
• 間違って使われることが多い
4.3 実際に起こりうる状況と戦略
• テスト自動化時にチームが直面するであろう典型的シナリオの紹介
4.3.1 新規プロジェクト (1/2)
• 理想を実現するチャンス
• 基礎的ルールを定めて、テスト基盤を構築すれば、継続的インテグレーションに向けた良いスタートが切れる– 変更のコストも低い
• 重要なのは一番最初から自動受け入れテストを書くこと• そのためには
– テクノロジープラットフォームとテストツールを選択する– シンプルな自動ビルドを準備する– INVEST(Independent-Negotiable-Valuable-Estimable-Small-Testable)原則に
従ったストーリーを受け入れ基準と合わせて導き出す
• その上で厳格なプロセスを実装– 顧客、アナリスト、テスターが受け入れ基準を定義– テスターは、受け入れ基準に従った受け入れテストを自動化する– 開発者は、受け入れ基準を満たすふるまいをコーディングする
– 自動テストが、種類問わず1つでも失敗したら優先度を最大にして修正する
4.3.1 新規プロジェクト (2/2)
• 顧客やプロジェクトマネージャーを含むチームの全員が、受け入れテストによる恩恵に賛同することが重要– テストの品質を犠牲にしてでも、早期のリリースを顧客が優先
するなら、従わないといけない– ただし、その結果何が起こるかははっきりさせておくべき
• 受け入れ基準を注意深く書き、ストーリーによってデリバリーされるビジネス価値を、ユーザ視点から表現するようにしておくことが重要– テストが保守できなくなる主要な原因は、下手に書かれた基準
のせい
• これらのプロセスに従った開発者のコードは、– カプセル化がうまく行われ、意図がわかりやすく、関心事が明
確に分離され、コードの再利用もよく行われている
4.3.2 プロジェクトの途中
• 導入するための最善の方法– 最も一般的で価値が高く重要なユースケースから始めるこ
と• 本当のビジネス価値がどこにあるかを明確に特定• その機能をテストを使ってリグレッションから守る• 価値の高いシナリオを網羅する正常パステストを自動化する
– 網羅するアクションの数を最大化しておくことは有益– 同じ機能に対して手動テストを2回以上やっていたら
• 今後も変更するかを確かめ、変更がなさそうなら自動化する
– 逆に、自動テストでよく修正しているものがあったら• その箇所のテストを無視する設定をする• (無視していいの?)
– 時間が無いときは、さまざまなパターンのテストデータを使って、カバレッジを保証するのが良い
4.3.3 レガシーシステム
• 存在しなければ自動ビルドを作ることを優先する– 作成はドキュメントがあれば容易、チームメンバーと話せればなお簡
単
– スポンサーなどの理解を得るには、リグレッションテストを作り、システムの機能を保護する価値を説明する
– 価値の高い機能を網羅する自動テストを幅広く作る
• 自動テストをレイヤ化する– 1、シンプルで高速に実装できるテスト– 2、特定のストーリー用の重要な機能のテスト– 新規機能は、新規プロジェクトのときと同様にする
• 自動テストを書くのは価値をデリバリーできるときだけにすべき– アプリは、機能を実装するコードと、フレームワークコードに分けら
れる– リグレッションバグはほとんど後者の変更が原因
– したがって後者を変更しない機能を追加する際は、包括的な自動テストを書くことにあまり価値がない
4.3.4 インテグレーションテスト(1/4)
• インテグレーションテストとは– アプリ内の独立した各部分が、依存しているサービスとう
まく連携できることを保証するテスト
• 受け入れテスト– 実際の外部システムや、サービスプロバイダの制御するレ
プリカに対して実行するもの– テストハーネスに対して実行するもの
• 本番以外で実際の外部システムを叩かずにアプリを安全にテストするために– 外部システムへのアクセスをファイアウォールで隔離– 擬似的な外部サービスと通信する設定を用意
4.3.4 インテグレーションテスト(2/4)
• 自前でテストハーネスを開発するケース– インタフェースは定義されているが、外部システムがまだ開発中
– 開発は終わっているが、テスト用のインスタンスがない
– テストシステムはあるが、レスポンスが入力によらず固定やランダム
– インストールが難しい or UIを通じて手作業の必要がある
– 外部サービスを含んだ機能用に、自動テストを書く必要がある
– テスト環境が、CIの負荷に耐えられない
4.3.4 インテグレーションテスト(3/4)
• 状態を記憶するサービスの場合の、テストハーネスはきわめて凝った作りになる– この状況で最も価値の高いテストは、ブラックボックステスト• 外部システムが返してくる可能性のあるレスポンスをすべて考慮し、そのレスポンスそれぞれに対してテストを書く
• モックでは、リクエストを識別して適切なレスポンスや、例外を返す必要がある
– 期待されるレスポンスだけでなく、予期せぬものもテストできるようにする必要がある• 可能な限り多くの異常な状況に対し、アプリが対処できることを確認する
4.3.4 インテグレーションテスト(4/4)
• 自動インテグレーションテスト– システムを本番にデプロイした際のスモークテストとして使える
– 本番システムを監視するための診断アプリとしても使える
– 統合の問題をリスクとして認識したなら、このテストは最も重要• テストサービスはあるか?性能は十分か?• サービスプロバイダのサポートはあるか?
• 本番バージョンで、キャパシティや可用性の問題を診断するテストができるか?
• APIは簡単にアクセスできるか?チーム内に専門家が必要か?
• 自分たちでテストサービスを開発、保守する必要は?• 外部サービスが期待通りにふるまわなかった際の、挙動は?
4.4 プロセス
• 受け入れテストを作る最適な方法– 各イテレーションの最初にステークホルダー(顧客、アナリスト、テスター)全員を集めて、打ち合わせを行い、テストする優先度が最も高いシナリオを決める• Cucumberなどのツールを使うと、受け入れ基準を自然言語のかたちで出力できる
• DSLを使うと、受け入れ基準をDSLで書ける
• 少なくとも、シナリオの正常パスを網羅するできる限りシンプルな受け入れテストを、顧客にその場で書いてもらう
– これにより• 開発者はストーリーの概要を適切にとらえ、最も重要なシナリオが何かを理解できる
• 開発者とテスター間のフィードバックサイクルを減らせる• 機能漏れやバグの軽減につながる
4.4.1 欠陥バックログを管理する(1/3)
• 欠陥バックログをうまく作る方法、あるいはそれにどう取り組むか– 欠陥ゼロ(ジェームズ・ショア)
• バグが見つかったらどんなときもすぐに修正されるようにしておく
• テスターがバグを早期に発見し、開発者がすぐに直せるようなチーム編成
– バックログがある場合、• 問題が誰にでもはっきりとわかるようになっていること
• 開発チームのメンバーが責任を持って、バックログを減らすためのプロセスを推進すること
4.4.1 欠陥バックログを管理する(2/3)
• 欠陥バックログの放置にはリスクがある
– バグを無視し、修正を先延ばしにし続けると、重大なバグもノイズの中に消える
– 受け入れテストがまったく無かったり、ブランチがtrunkから離れすぎて受け入れテストが効果的でないと、問題はさらに悪化する
– 詳細な議論は14章
4.4.1 欠陥バックログを管理する(3/3)
• 欠陥を機能と同じように扱うアプローチ
– 機能と、特定のバグを比べて、どちらの優先度が高いかを決めるのは顧客の仕事
– バグを分類し、優先度をつけることは意味がある
• 致命的、作業の妨げになる、中、低
• 発生頻度、ユーザ影響、回避方法の有無を考慮するアプローチもある
– バグが分類できれば、ストーリーと同様に優先順位を付けることができ、一緒に見ることができる
4.5 まとめ
• 高品質なソフトを作るには、
– デリバリーに関わるすべての人が、テストに責任を負う
– プロジェクト初期から実践する
• テストの第一目的は、開発、設計、リリースを駆動するフィードバックループを構築すること
– 最も短いフィードバックループは、システム変更時に実行される自動テスト一式によって作られる
• テストは「完了」の定義と本質的に相関している– テストによって機能の1つ1つが理解できる
– プロセスを通じてどこでもテストが実行されるようにする
感想
• ニホンゴムズカシイ
• 割と拷問
• 普段はRspecで、Model と Controllerのテストしか書かないので、Cucumberを使った
受け入れテストを一度試してみたいと思った