この章ではテストに関する思想について述べる
- 思想がテスト自動化に与える影響は大きい
- xUnitベースのテスト自動化をどう実施するかについて、思想上の差異が存在する
- たとえば「ある人々はMock Objectsをあまり使わないのに、他の人々はどこでも使う」という理由は思想上の差異に起因する
- 議論から以下の思想上の差異が明らかになった
- 「テストが後」か「テストが先」か
- 「1つづつテスト」か「一度に全部テスト」か
- 「アウトサイド・イン」か「インサイド・アウト」か(これは、設計とコーディング両方に独立して適用される)
- 「振る舞いの検証」か「状態の検証」か
- 「テストを進めながらフィクスチャの設計」か「大きなフィクスチャの事前設計」か
- 伝統的なソフトウェア開発は全てのソフトウェアの設計およびコーディングが終わってからテスト(カスタマーテストとユニットテストの両方)を実行する
- アジャイルコミュニティではテストを先に実行するのが標準的なやり方
- テストが先の方が良いと主張する理由:
- レガシーシステムに対して完全に自動化されたテストを作るのは非常に困難
- 「ほとんどソフトウェアが完成した」後に自動ユニットテストを書くという規律を持ち続けるというのは、テストそのものの構築が簡単かどうかにかかわらず難しい
- テストしやすいように設計したとしても、製品コードを変更せずにテストが簡単かつ自然に掛けるという可能性は低い
- テストを先に書けば、設計は自然にテスト可能になる
- テストを先にかけば、テストが通るのに必要なだけのコードだけを書くようになり、製品コードはより小さくなる
- オプショナルな機能は書かれなくなる
- 実際には動作しない手の込んだエラー処理コードに費やされる労力はなくなる
- テストの必要性に基づいて必要なメソッドだけを提供するので、テストがよりロバストになる
- フィクスチャのセットアップや結果の検証を目的としたオブジェクトの状態へのアクセスはより自然になる
- アサーションで文字列表現を比較するのではなく、オブジェクトの属性を使うことにより、「(不吉な)におい:繊細な同値性」を避ける事ができかもしれない
- 文字列表現を実装する必要がまったくないことに気がつくかもしれない
- 「置換可能な依存関係」が当初からソフトウェアに設計されることから、結果の検証を目的としたテストダブルによる依存関係の置換能力は大きく向上する
- テストファーストに関するよくある質問:「どうやって存在していないソフトウエアのテストをかくの?」
- 議論を「例」と「例駆動開発(EDD)」について語るように再構成すると理解されやすい
- 「例」は「テスト」より「コートの前に書く」ということが想像しやすい
- RubyのRSpecやJavaのJBehaveなどがEDDフレームワークとして出現している
- EDDフレームワークは基本設計はxUnitと同じだが、用語が「実行可能な仕様」という思想を反映して変更されている
- ビジネスロジックを含むコンポーネントの仕様記述のための、他の選択肢としてはFITがある
- これらは常に技術系でない人々にはプログラム言語で書かれた何かよりも読みやすいだろう
- テスト駆動開発ではテストを「ちょっと書き、コードをちょっと書き、テストをもうちょっと書く」、すなわちインクリメンタル開発が推奨される
- コードを書く前に現在の機能に必要な全てのテストを決定するのを好む開発者も存在する
- 後者のやり方だと「顧客のように考える」「テスターのように考える」事が可能になり「解決モード」に早くなりすぎることを防ぐことができる
- テスト駆動開発の利点
- テストを一度に一つにしてソフトウエアを開発していったほうが、よりインクリメンタルに設計ができる
- 一つのテストのみが失敗している状態の方が、集中力を保つのが容易
- テストが失敗する理由が明確(問題を発生させた最後の変更が頭のなかに残っている)なので、不具合局在化ができる
- 上記の利点は、ユニットテストについては特に妥当
- 適切な妥協案:
- 全てのユニットテストをタスクの最初で(空のテストメソッドを書いて)同定するが、一度には一つのテストメソッドの中身のみをコーディングする
- は全てのテストメソッドの中身を書いてしまってから、一つを残して無効にしておいて、そのテストに関する製品コードを書く
- カスタマーテストに関しては、一つのストーリに関しての全テストを最初に用意するのが妥当
- 認識された(しかし十分に肉付けされていない)ストーリーについて、工数見積を尋ねられる前にカスタマーテストを持っておくことも、それがストーリーの枠付を促進するので有効
-
「アウトサイド・イン」はシステム全体のブラックボックスカスタマーテスト(ストーリーテスト)を考えることから初めて、その後ユニットテストを考えること(このやり方では大きい粒度のコンポーネントに関するコンポーネントテストを実装するかもしれない)
-
これらのテストは「開発者のように考える」前に「顧客のように考える」ことを促進する
-
設計は「アウトサイド・イン」だがコード作成は(依存関係問題を避けるために)「インサイド・アウト」にやること(図4.1)を好む開発者もいる
-
この戦術では、
- 内部のソフトウェアのテストを書くときに、「外部のソフトウェアのニーズ」を予想する必要がある
- 内部のソフトウェア無しでは外部のソフトウェアのテストはしない
- テストスタブやモックオブジェクトの必要性を避ける事ができる(内部コンポーネントが特別な値や例外を返すテストでは、内部コンポーネントがそれをできなければテストスタブが必要な事もある)
-
設計もコードも「アウトサイド・イン」でやること(図4.2)を好むテスト実行者もいる
-
この戦術では
- 依存関係問題を取り扱う必要がある(テストスタブが使える)
- テストダブルはあとで削除することもできる(残しておくと不具合局在性は高まるが、テストの保守コストは潜在的には増大する)
- 「状態主義者」の見方: SUTを特定の状態に起き、実行し、テストの最後にSUTが期待している状態にあるかを検証する
- 「振る舞い主義者」の見方: SUTの最初と最後の状態を指定するのみならず、SUTが依存関係物に対して何を呼び出すかを指定するべきである(「間接的出力」も関数の戻り値同様出力なので)
- 「振る舞い主義派」の考え方は「振る舞い駆動開発」と呼ばれる
- 振る舞いの検証は個々のユニットのテストの独立性を向上させる点ではよいが、リファクタリングが困難になるという追加のコストが発生する
- 伝統的なテストコミュニティでは、アプリケーションと予め様々なテストデータが設定されたデータベースを含む「テストベッド」を定義する事がk飲まれる
- xUnitテストで同じやりかたをやる場合は1つ以上のテストケースクラスのすべてのテストメソッドで使える「標準フィクスチャ」を定義できるが、フィクスチャのどの部分が特定のテストメソッドの本当の事前条件なのかを切り分けるのは難しくなる
- よりアジャイルなアプローチは各テストメソッド用に「最小のフィクスチャ」を個別に設計すること
- いつでも同じ思想に従わせることはできない
- どのような思想に同意しているのかを理解することは、何故物事を違うように考えるのかということを理解するのに有用
- ゴールを共有していないのではなく、ゴールをどう達成するかを違う思想で決定している事がある
- 異なる思想が存在することを理解し、自分たちがどの思想に同意しているのかを認識することが、共通基盤を見出す最初のステップ
- テストは先に各
- テストは例である
- 普段は一度の一つのテストを書くが、時々事前に全てのテストをスケルトンとしてリス化する
- アウトサイド・イン開発は次の内部レイヤ向けにどのようなテストが必要なのかを明確にする
- 主に「状態検証」を使うが良いコードカバレッジが必要なときに「振る舞い検証」を使う場合もある
- テストを進めながらフィクスチャの設計をする