テスト自動化のゴールについて

テスト自動化のゴールは、以下の観点がある。

<テストにより生み出される価値の観点>

  • (1) テストは品質向上の手助けになるべき
  • (2) テストはSUT(テスト対象)を理解する手助けになるべき
  • (3) テストは(リスク生み出さず)リスクを減らものになるべき

<テスト自体のあるべき姿の観点>

  • (4) テストは簡単に実行できるべき
  • (5) テストは記述とメンテナンスが簡単であるべき
  • (6) テストはシステムが進化しても最小のメンテナンスコストで対応できるべき


1. テストは品質向上の手助けになるべき

品質には2つの視点がある。

  • ソフトウェアは正しく作られているか?
  • 我々は正しいソフトウェアを作ったか?

ゴール:仕様としてのテスト(Tests as Specification)

  • テスト駆動開発テストファーストを実践している場合、SUTをビルドする前にテストによってそのSUTが何をすべきかを把握できる。
  • 「正しいソフトウェアを作っているか」を確認するために、SUTがどのように利用されるかをテストが反映したものでなければならない。
  • 様々なテストシナリオを詳細に検討することで、要求が不明確であったり矛盾している箇所を特定できるので、結果として仕様の質を向上できる。

ゴール:バグを寄せ付けない (Bug Repellent)

  • テストはバグを見つけるもの。
  • 自動テストはバグが生み出されるのを防ぐもの。
  • コードのチェックイン前にリグレッションテストが自動的に行われるようにしていれば、チェックインしたコードに既存のバグは含まれていないといえる。

ゴール:欠陥箇所の特定 (Defect Localization)

  • ユニットテストでどのテストが失敗したかによってバグの箇所を素早く特定できるべき。
  • カスタマーテストで失敗した場合、カスタマーが期待する振る舞いでないことを示してくれる。そして、失敗したユニットテストはその理由を示してくれる。このことを「欠陥箇所の特定」と呼ぶ。
  • もしカスタマーテストが失敗しているにもかかわらずユニットテストが失敗していないとしたら、ユニットテストの欠如を示している。

2. テストはSUTを理解する手助けになるべき

バグを寄せ付けないことだけがテストの役割ではない。テストは、テストを読む人にコードがどのように動くことを想定されているかを示すことができる。ブラックボックスコンポーネントのテストは、そのコンポーネントの仕様を表している。

ゴール:ドキュメンテーションとしてのテスト (Tests as Documentation)

  • テストの自動化によって、ドキュメンテーションとしてテストを利用できる。テストはどのような動作結果となるかを示してくれる。
  • システムの詳細な動作を把握したい場合は、デバッガーを利用してテストを実行し、コードの各ステップがどのように動作しているか確認できる。

3. テストはリスクを減らすものになるべき

ゴール:セーフティーネットとしてのテスト (Tests as Safety Net)

  • 自動的なリグレッションテストがないようなレガシーコードを扱う場合、ソースコードの変更は非常にリスキーである。変更によってどこかを破壊してしまったかどうか確認するすべがないからである。
  • 一方、自動的なリグレッションテストがある場合、ソースコードの変更がすばやく行える。コードの変更によってどのテストが失敗したかわかり、例えばあるパラメータが何のために必要なのかがわかったりする。
  • つまり、テストの自動化によってとりあえず何か変更したときのセーフティーネットになる。もし不足しているテストがあれば、それはセーフティーネットの穴になりえる。

ゴール:害を与えてはならない (Do No Harm)

  • 「テスト自動化を導入することでどのようなリスクがあるか」という議論がある。テスト自動化によってSUTに新たな問題をもたらしてはいけないように注意すべきである。
  • The Keep Test Logic Out of Production Code という原則は、SUTにテストに特化した仕組みを混入するリスクを避けるようにするためのものである。
  • 実際にはテストできていないのにソースコードがテストされたと思いコードが信頼できると信じてしまうリスクもある。例えば、Test Doublesを多用してしまってSUTの大部分を置き換えてしまったことで、実際には、SUTがテストできていない場合がある。これは、もう一つの原則である Don't Modify the SUTに通じる。つまり、SUTのどの部分をテストできているのか、SUTのどの部分をテスト固有のロジックによって置き換えているかを把握しておかなければならない。

4. テストは簡単に実行できるべき

もっと頻繁にコード変更できるように、テストがセーフティーネットとなるには、テストが簡単に実行できて頻繁に自動テストを走らせられる必要がある。テストを簡単に実行するには次の4つのゴールが必要である。

  • テストは、なんの追加作業もなく実行できるように Fully Automated Tests でなければならない。
  • テストは、手動操作を介在せずエラーを見つけてリポートできるよう Self-Checking Tests でなければならない。
  • テストは、何回実施しても同じ結果となるように Repeatable Tests でなければならない。
  • 理想的には、各テストは単体で実行できるよう Independent Test であるべき。

ゴール:完全に自動化されたテスト (Fully Automated Test)

  • 手動操作の介在(Manual Intervention)を必要としないテストを Fully Automated Tests という。
  • Fully Automated Tests は、他のゴールを達成するための前提条件である。テスト結果を確認せず、1度しか実行できないような Fully Automated Tests を作成することもできる。
  • Fully Aotomated Tests をベースとして、次に述べる Self-Checking Tests や Repeatable Test というゴールを実現する。

ゴール:セルフチェッキングテスト (Self-Checking Test)

  • Self-Checking Test は、テストが期待する結果が正しいことを検証できるテストのことである。
  • テストランナーは、Holly-wood principal (連絡しないでくれ、こちらから連絡するので)の考え方に基づいている。つまり、テストランナーは、テストがパスしなかったときだけ通知する仕組みである。
  • 多くのテストランナーでは、すべてのテスト結果が正しい場合に green bar を表示し、テストのいずれかが失敗して詳細な確認が必要な場合に red bar を表示する。

ゴール:繰り返し可能なテスト (Repeatable Test)

  • Repeatable Test は、手動操作を介在せず、何回連続して実行しても必ず同じテスト結果となるものである。
  • テスト実行前に手動操作を必要とする Unrepeatable Tests や、同じテストで異なっ結果となるような Nondeterministic Tests だと、テストが失敗した原因調査に多くの時間を要してしまう。
  • Unrepeatable Tests の原因としては、Shared Fixtureを使っている場合などが考えられる。そのような場合は、Automated Teardown などを利用してテスト前のごみを事前に排除するような処理が必要になる。

5. テストは記述とメンテナンスが簡単であるべき

テストコードを書く場合、テストコードを書くことよりもむしろテストすることにフォーカスすべきである。つまり、テストはシンプルでなければならない。変更に少ない労力で対応できるように、重複するようなテストは極力最小化しなければならない。テストを複雑にする理由は次の2つ。

  • 1つのテストでたくさんの機能を検証しようとしてしまう。
  • テストスクリプト言語(例えば、Java)と、テストで表現しようとするドメイン知識間での事前/事後関係において「表現上のギャップ」があまりにも大きい。

ゴール:シンプルテスト (Simple Tests)

  • テスト駆動開発では、コードは一度に一つのテストをパスするよう書かれていくので、テストをシンプルに保つことは非常に重要。
  • 分離された Test Method で Verify One Condition per Test であるべき。
  • 例外的な条件としては、アプリケーションのリアルな操作シナリオを表現するようなカスタマーテストを書く場合である。

ゴール:表現的なテスト (Expressive Tests)

  • Test Utility Methods, Creation Methods, Custom Assertion といったドメイン固有の処理を高次元化することで「表現上のギャップ」に対処できる。
  • DRY (Don't Repeat Yourself) の原則は、テストコードに対しても適用されるべき。
  • ただし、テストは、読み手に意図を伝えるようなものであるべきなので、コアとなるテストロジックは Test Method にとどめておいたほうがよい。

ゴール:関心事の分離 (Separation of Concerns)

  • 関心事の分離には、2つの側面がある。
  • テストコードはプロダクションコードから分離する
  • 各テストケースはひとつの関心事に集中する
  • 悪い代表例として、ビジネスロジックとUIを同じ場所でテストすることが挙げられる。どちらかの関心事(例えば、ユーザーインターフェース)が変更すると、すべてのテストを修正しなければならなくなる。

6. テストはシステムが進化しても最小のメンテナンスで対応できるべき

ゴール:強固なテスト (Robust Test)

  • 開発プロジェクトや要求の変更によって、コード変更は避けられない。コード変更に対して、テストコードの変更は、極力小さいものにしなければならない。
  • テストの重複はなるべく最小化し、テスト環境の変更によるテストへの影響も少ないものにしなければならない。つまり、SUTをテスト環境からなるべく分離し、 Robust Tests にすべき。
  • Verify One Condition per Test, Test Utility Methods といった手法は変更の影響を抑える手法である。

//