【読書メモ】継続的デリバリ―④ 4章 テスト戦略を実装する

今までの記事

- [【読書メモ】継続的デリバリ―① 1章 ソフトウェアデリバリーの問題](https://qiita.com/Jazuma/items/180b46947167b6347bef)
- [【読書メモ】継続的デリバリ―② 2章 構成管理](https://qiita.com/Jazuma/items/eb85047b6f72c1df260e)
- [【読書メモ】継続的デリバリ―③ 3章 継続的インテグレーション](https://qiita.com/Jazuma/items/e5ec2646d10f88d2965f)

### 4.1 導入

本章では効果的な自動テストシステムの計画を立て、それを実装することを扱う。また、テスト自動化戦略を立てて自動化を可能にするプラクティスを解説する。

テストはプロジェクトの最初から継続的に実施しなければならない。これにより品質が作り込まれる。
品質を作り込むとは自動テストを複数の抽象度で書くことであり、自動テスト戦略を継続的に改善することでもある。

テスト戦略の設計は**プロジェクトのリスクを識別して優先順位をつけ、そのリスクを緩和するためにどんなアクションを取ればよいかを決定するプロセス**である。

### 4.2 テストの種類

テストには4つの観点がある。すなわちビジネス観点からか、技術の観点からか、また 開発プロセスをサポートしているか、プロジェクトを評価するのに使われるかである。

#### 4.2.1 開発プロセスをサポートするビジネス視点のテスト

これに分類されるのは機能テストあるいは受け入れテストと言われる。受け入れテストはストーリーに対する受け入れ基準が満たされていることを保証するテストである。 受け入れテストはストーリー開始前に書く必要があり、自動化されていることが望ましい。

受け入れテストがパスすればテスト対象の要件やストーリーが何であれ完了したと言える。したがって要件に対する成功条件を定義した上で顧客やユーザが受け入れテストを書くのが望ましい。CucumberやJBehave、Twist等の機能テスト自動ツールはこの理想を実現しようとしている。

#### 4.2.2 受け入れテストを自動化する

自動受け入れテストには以下のようなメリットがある。

- 受け入れテストを実行すれば要件を満たしたか確認できるため、フィードバックを素早く得ることができる。
- テスターの負荷を軽減する
- テスターが反復作業する必要がなくなり、探索的テストやより価値の高い作業に集中できるようになる
- 受け入れテストがデグレを検知する手段になる

しかし、すべてのテストを自動化する必要はない。ユーザビリティやデザインのテストは人間が手動で実施する方がうまくいく。探索的テストも自動で実施することはできない。

手動テストを繰り返し実行するためにどの程度時間がかかっているか調べて自動化を検討する必要がある。
一般的に言って同じテストを2回繰り返したら自動化するとよいと言われる。(ただし、テストの保守に時間がかからない場合に限られる)

自動テストで最も重要なのは主要な正常パスに対するテストである。正常パスのテストは変更が機能を壊していないかを検知してくれる。

さらに余裕がある場合、代替パスと以上パスのどちらを優先して自動化するか選ぶのは難しい。アプリケーションが安定していれば代替パスをテストした方がよい。ユーザ定義のシナリオだからである。

#### 4.2.3 開発プロセスをサポートする技術視点のテスト

これらのテストは開発者によって書かれて保守される。この分類にあてはまるのはユニットテスト・コンポーネントテスト・デプロイメントテストである。ユニットテストはクラスやメソッドを個別にテストする。ユニットテストではデータベースにアクセスしたりファイルシステムを使ったり外部と通信してはならない。一般化するとシステムのコンポーネント間でやりとりしてはならない。これによりユニットテストの実行時間が短くなり、変更によってシステムが壊れてないかすぐに確認することができる。

しかし、ユニットテストではコンポーネント間でやり取りが行われることによるバグを検知することができない。
コンポーネントテストではより大きな粒度でテストするためこのような不具合を検知することができる。
コンポーネントテストはユニットテストよりも実行速度が遅い。データベースやファイルシステムと通信することがあるためである。

デプロイメントテストはアプリケーションをデプロイする時に実施される。このテストではデプロイが成功したことを確認する。 つまり、アプリケーションが正しくインストールされ、正しく設定され、外部サービスと接続できることを確認する。

#### 4.2.4 プロジェクトの評価をするビジネス視点のテスト

このカテゴリーのテストは手動で実施する。期待されている価値をアプリケーションが提供できていることを確認する。仕様が満たされていることに加えて仕様が正しく価値を提供できているかも確かめる必要がある。

顧客に対するデモはプロジェクトの評価をするテストにおいて最も重要である。機能が完成してからではなく、開発中にも定期的にデモを実施すべきである。そうすれば仕様に問題があったとしても早期に検知することができるためである。

探索的テストは単にバグを見つけるだけではなく、自動テストを新しく作ることにもつながる。潜在的にはアプリケーションに対する新しい要件のための素材も提供する。

#### 4.2.5 プロジェクトを評価する技術視点のテスト

このカテゴリーに当てはまるのは受け入れテストである。受け入れテストには機能テストと非機能テストの2種類が存在する。 非機能テストでは機能以外のシステム品質を全てテストする。キャパシティや可用性、セキュリティなどである。

非機能要件テストは実行に特殊な環境が必要であったり、実行時間が長くなる傾向がある。
したがって、後回しになりやすい。自動化されている場合でも実行頻度が低くなりやすい。

しかし、このような状況は変わりつつある。非機能要件をテストするツールは充実してきており、それを利用するノウハウも蓄積されてきている。 プロジェクトの開始時に基本的な非機能要件をいくつかテストするよう準備することが推奨される。

#### 4.2.6 テストダブル

自動テストにおけるポイントの1つに、システムの一部をテスト用のものに置き換えるというものがある。
こうするとテスト対象と外部とのやりとりが単純化されるので、振る舞いを簡単に決定できるようになる。

このようなテスト用の部品はモックやスタブと呼ばれている。一般的な用語としては「テストダブル」が使われることが多い。 それぞれのテストダブルには以下のような特徴がある。

- ダミーオブジェクトは渡されるが、実際に使われることはない。パラメタリストを埋めるためだけに使われる。
- フェイクオブジェクトは実際に動作するように作られている。しかし、本番稼働には向かない。典型的な例としてインメモリデータベースが挙げられる。
- スタブはテスト中に呼び出された際、固定のレスポンスを返す。通常はテスト用プログラム以外に対してはレスポンスを返さない。
- スパイはスタブの一種だが、どのように呼ばれたかについての情報をある程度記録する。
- モックは呼び出し内容を定義したエクスペクテーションがあらかじめプログラムされる。期待されていない方法で呼び出された場合は例外をスローする。

### 4.3 実際に起こり得る状況と戦略

#### 4.3.1 新規プロジェクト

新規プロジェクトは継続的インテグレーションを実演するのに都合が良い。重要なのは自動受け入れテストを最初から書くことである。そのために以下のことを実施する必要がある。

- プラットフォームとテストツールを選択する
- シンプルな自動ビルドを準備する
- INVEST原則に沿ったストーリーを受け入れ基準と合わせて導き出す。

その上で精密なプロセスを定義すべきである。

- 顧客・アナリスト・テスターが受け入れ基準を定義する
- テスターは開発者と作業して受け入れ基準に従った受け入れテストを自動化する
- 開発者は受け入れ基準を満たす振る舞いをコーディングする
- 自動テストが1つでも失敗した場合、開発者はそれを修正する

このようなプロセスはできれば開発の途中からではなく、プロジェクト開始時点から実施しておくと良い。

また、重要なのは受け入れ基準を入念に書いてストーリーによってデリバリーされるビジネス価値をユーザの視点から表現するようにすることである。下手な受け入れ基準を自動化するとテストスイートを保守できなくなることが多い。

#### 4.3.2 既存プロジェクト

既存プロジェクトで自動テストを導入するためには価値が高く重要なユースケースから始めることが一般的である。そのためには顧客と会話してビジネス価値を特定し、テストでその機能をデグレから守るのである。

また、このようなテストがなるべく多くのアクションをカバーできると有益である。

時間に余裕がない場合は多くのシナリオをスクリプト化することはできない。その場合、テストデータのパターンを増やしてカバレッジを増やすのが良い。テストの目的を定義してその目的を果たすためのなるべくシンプルなスクリプトを用意するのである。

#### 4.3.3 レガシーシステム

レガシーシステムにおいては「変更するコードについてテストを書く」という方針が有効である。
テストを作成するにあたり、自動ビルドを優先して作成する。その上で自動テストの基盤を構築していく。

顧客やビジネスサイドの人間は既存機能にテストを追加することは価値が低いと判断して反対するかもしれない。だからこそ価値の高い機能をテストで保護すると説明することが重要である。また、新しく追加する振る舞いに対してはテストをインクリメンタルに追加すればよい。このようなテストがレガシーシステムに対するスモークテストになる。

スモークテストの準備ができたらストーリーを開発することができる。この際、自動テストをレイヤー化するのが役に立つ。最初のレイヤーはシンプルで高速実行できるものにすべきである。このテストがあれば機能開発時の助けになる。二番目のレイヤーでは特定のストーリー用の重要な機能をテストする。

#### 4.3.4 インテグレーションテスト

アプリケーションが外部システムと通信していたり、疎結合なモジュール同士が連携して成り立っている場合インテグレーションテストが重要になる。ここで言うインテグレーションテストとはアプリケーション内の独立した部分が依存先とうまく連携できることを担保するためのテストを指す。

インテグレーションテストは2通りのコンテキストで実行すべきである。1つはテスト対象のシステムを依存先の外部システムやサービスプロバイダの制御するレプリカに対して実行する。第二に、コードベースの一部として作成されたテストハーネスに対して実行する。

テストでは実際の外部システムを呼び出すべきではない。あるいはテストのためにダミーのリクエストを送信していることをそのサービスに伝える必要がある。そのための方法として以下2つの方法がある。

- テスト環境において外部システムへのアクセスをファイアウォールで遮断する。外部システムと通信できない場合の挙動をテストする際にも役に立つ。
- アプリケーションが疑似的な外部システムと通信できるようにする設定を用意する。

外部システムにレプリカテストサービスがあり、本番と同じように動くのであればそのサービスを使うべきである。しかし、実際はテストハーネスを開発しなければならないことが多い。 以下のような場合である。

- 外部システムのインタフェースは定義されているが、開発中である
- 外部システムの開発は終わっているが、テスト用のインスタンスが用意されていない
- テストシステムは存在するが、レスポンスが入力によって決定されるわけではなく、自動テストで結果を検証することができない
- 外部システムがインストールしづらい。また、UIを通して手作業する必要がある。
- 外部サービスを含んだ機能用に、標準的な自動受け入れテストを書く必要がある。
- 自動化された継続的インテグレーションシステムの負荷や、求められるサービスレベルにテスト環境が堪えることができない

サービス呼び出しに対する正常系のレスポンスだけではなく、例外的なものについてもテストハーネスで複製することが必要である。

自動インテグレーションテストはスモークテストや本番システム監視用の診断アプリケーションとしても使える。外部システムとの統合をリリース計画に組み込む必要がある。以下のようなことを検討しなければならない。

- テストサービスを利用できるか？また、性能や機能は十分か？
- サービス提供元のサポート・開発体制は十分か？
- サービスのAPIは簡単にアクセス可能か？あるいはチーム内に専門的スキルを持った人物が必要か？
- テストサービスを書いたり保守する必要があるか？
- 外部サービスが期待通り動かなかった場合に自分たちのシステムはどのように動くべきか？