Open12

xUTPまとめ

やんやんやんやん

Test Smells

The kind of Smells

The Project Smells

プロジェクトの匂いは、そのプロジェクトで何かがうまく行ってないことを示す症状。
プロジェクトマネージャーはテストを書かないため、プロジェクトの匂いはテスト自動化の部分で何かが完璧ではない可能性を示す最初のヒントになる。
プロジェクトマネージャーは機能、品質、リソース、コストに重きを置くので、プロジェクトの匂いはそういった問題に集中する傾向がある。
自動テストがあるのにもかかわらず、プロダクションでのバグが多かったり、テストのメンテナンスコストが高い場合、テストを書くためにコストをかけるより、プロダクションコードを書いたほうがよいとプロジェクトマネージャーは判断してしまうだろう

The Behavior Smells

動作の匂いはコンパイルエラーやテスト失敗という形で現れる。最も一般的なのはFlagile testである。これは一度通ったテストが何らかの理由で失敗し始めるときに発生する。
Flagile testの原因は、

  • Interface Sensitivity
    • テスト時に現れる何らかのinterface (UIとか) の変更でテストが壊れる
  • Behavior Sensitivity
    • SUT (テスト対象) の変更によってテストが壊れる
    • 1つの変更で、たくさんテストが壊れたら問題
  • Data Sensitivity
    • SUTのデータを変更したら壊れる
  • Context Sensitivity
    • SUTが依存する環境の違いによって壊れる。日付とか

Data / Context Sensitivity はFlagile Fixture というものの一例で、フィクスチャを変更すると複数のテストが失敗してしまう。こういうときはテストの設計が悪い。

生産性低下の要因の一つは頻繁なデバッグである。自動化されたユニットテストはデバッグの必要性がなくなるべきである。なぜなら、失敗したテストによってなぜ失敗したかが明らかになっているから。
完全自動化テストのいいところは頻繁にテストができることなので、もう一つの生産性定価の要因として遅いテストがある。テストが遅くなると、人々はテストを頻繁に実行しなくなる。そしてFBを得るスピードが遅れ欠陥に気づくのが遅れる。

The Code Smells

テストを読むときの明白な匂いとして、Obscure Test (曖昧なテスト)がある。つまり、そのテストが何をテストしたいのかという意図がわからないので、メンテナンスコストが増大するし、メンテナンスの結果間違った修正をしてしまう可能性もある。他にも Conditional Test Logicが関連した匂いとして挙げられる。
テスト似複数の実行経路があると、各ケースでどのようにテストが実行されるかわからない。

やんやんやんやん

Goals of Test Automation

テストコードはオプションなので、テストを維持するのが大変になってしまったり、コストがかかるようになってしまうと、テストを諦めてしまう誘惑に駆られてしまう。
著者は、これまでの経験から成功した解決策は

  • 目標: 達成スべきこと
  • 原則: 達成するための方法
    の構成要素に分けて考えてきた。目標と原則を守ることで、書きやすく読みやすく保守しやすい自動テストができる。

Economics of Test Automation

自動化されたテストスイートを構築し、維持するには常にコストがかかる。
我々の目標はソフトウェア開発のコストを増加させないようにすること。なので、自動テストの構築と維持にかかるコストはやらなかった場合におきる手動テストとデバッグなどの削減分や欠陥の修正コストで相殺される必要がある。

Goals of Test Automation

  • テストは品質向上に役立つものでなくてはならない
  • テストはSUTの理解を助けるものでなくてはならない
  • テストはリスクを減らすものでなくてはならない
  • テストは実行しやすいものでなくてはならない
  • テストは書きやすく、保守しやすいものでなくてはならない
  • テストは、システムが進化しても最小のメンテナンスしか必要としない

最初の3つはテストが提供する価値、後の3つはテスト自体の特徴に焦点を当てている。

やんやんやんやん

Tests Should Help Us Improve Quality

ソフトウェアのクオリティは伝統的に次の2つのカテゴリに分類される

  1. ソフトウェアは正しく作られたか?
  2. 我々は正しいソフトウェアを作ったか?

Goal: Bug Repellent

自動化テストはバグが起きるのを防ぐ目的がある。なぜなら、コードを本番にデリバリーする前にバグに気づかせてくれるからだ。

Goal: Defect Localization

間違いは起こるもので、いくつかの間違いは修正するよりも防ぐほうがコストがかかる。
ユニットテストがかなり小さいものであれば、どのテストが失敗したかに基づいてバグを特定することができる。
もし、統合テストが失敗するのにユニットテストが失敗しない場合、ユニットテストが足りてない箇所があることを示す。つまり、ユニットテストのメリットを享受するには、すべてのユニットを網羅するテストを書く必要がある。また、テスト自体にバグがある場合も利点を享受できない。なので、テストはシンプルな必要がある。

やんやんやんやん

Tests Should Help Us Understand the SUT

テストはバグを撃退するためだけのものではない。コードがどのように動作するのかを示すこともできる。

Goal: ドキュメントとしてのテスト

もしテストがなければ、我々はSUTのコードを読みながら「もし入力がxxxのときは結果はyyyになるはず...?」みたいな調査をする必要がある。自動化されたテストがあれば結果がどうなるかが対応するテストを見ればわかるはずである。

やんやんやんやん

Tests Should Reduce (and Not Introduce) Risk

テストは要件をより適切に文書化し、インクリメンタルな開発のさなかにバグが忍び込むのを防ぐことでソフトウェアの品質を向上させる。これはリスク低減の一形態であり、他にもアプリケーション全体をブラックボックスとして従来のテストでは誘発されなかった不可能な状況における動作を検証することがある。
自動化テストによって、どんなリスクが軽減されるかブレストするのが大事。

Tests as Safety Net

テストが書かれていないレガシーコードをいじるのは怖い。なぜなら、どこかに変更を加えたときにどこが壊れるかがわからないからだ。その一方で、テストが書かれていれば変更を加えたことで何かが壊れたらテストが失敗するのですぐにきづくことができる。このようにテストはセーフティネット的な役割を果たす。セーフティネットとしての有効性はテストがどれだけ挙動を網羅しているかによる。

Do No Harm

自動テストがSUTに新たな問題をもちこまないように注意する必要がある。Keep Test Logic Out of Production Codeの原則は、テストのためのコードをSUTに入れないようにすることを支持している。
テストのためのコードはテストのときのみ使われるべきで、SUTの中に置かれるべきではない。
もう一つ犯しがちな間違いとして、テストダブルの濫用である。どのSUTをテストしているのかを明確にし、テストしている部分をテスト固有のロジックに置き換えてはいけない。

やんやんやんやん

Tests Should Be Easy to Run

開発者が自動テストを頻繁に実行するのは、テストが実行しやすいときだけです。
テストを実行しやすくするための具体的な目標が4つある。

  1. 完全に自動化されたテストであり、労力をかけずに実行できること
  2. 自己チェックテストであること。つまり、手動で検査することなくエラーを検出し報告できる
  3. 繰り返し可能なテストであること。 何回テストを回しても同じ結果が得られること
  4. 理想的には各テストが独立に実行できること

これらが満たされていれば、簡単な操作(ボタンの1clickとか)だけでテストによるフィードバックを得ることができる。

Fully Automated Test

つまり、手動での介入無しで実行できるテストのこと。これは、他の基準を満たすために前提条件である。

Self-Checking Test

Self-Checking Testには、期待される出力が正しいことを検証するために必要なものが全て組み込まれている。Hollywoodの法則に従って、テストが失敗したときだけテストランナーが我々を呼び出す。

A Repeatable Test

連続して何度も実行することができ、人間が介在しなくても再現性がある。再現性のないテストはテスト実行時のオーバーヘッドを大きく増加させる。
UnRepeatable なTest以外には、時間によって結果が異なる非決定性テストも悪い例だ。
正当な理由なしにテストがコケると、開発者はテストの失敗という結果を無視し始める。

やんやんやんやん

Tests Should be Easy to Write and Maintain

我々は、テストのコーディングよりもテストそのものに集中しなければならない。そのためにはテストはシンプルでなければならない。これは読むとき書くとき両方で言えることだ。
テストが複雑になってしまう原因はふたつある。
一つは1つのテストで多くの機能を検証しようとすることだ。

Goal: Simple Tests

テストはシンプルに書かれているべきだ。なぜなら、コードは一度に1つのテストを合格するように書かれ、各テストはSUTに新しいふるまいを1つだけ導入するようにしたいからだ。
例外は顧客テストで、ユーザーがどのようにソフトウェアを使用するかを文書化する方法なので、その使用法が長いステップを踏む場合、テストでもそれを反映する必要がある。

Goal: Expressive Tests

表現力のギャップはドメイン固有のテスト言語を構成するテストユーティリティメソッドのライブラリを構築することで解決できる。Creation Methods, Custom Assertion などはいい例だ。(後述される)
DRY原則はテストコードに対しても適用していくとよいが、テストは意図を伝えるべきものなので、核となるロジックは各テストメソッドに残し、一箇所で確認できるようにするのが良い。

  1. テストコードをプロダクションコードから分離したい
  2. それぞれのテストが単一の問題に集中するようにしたい

以上2つを達成するには、Obscure Testを回避する必要がある。やってはいけないことの最たる例がビジネスロジックのテストをユーザーインターフェースと同じテストでやること。一度に1つの問題をテストする場合、ロジックを異なるコンポーネントに分離する必要がある。

Tests Should Require Minimal Maintenance as the system Evovles Around Them

変更を容易にするためにテストを書いているので、不用意に変更を困難にしてしまうことがないように務めるべきである。あるクラスのメソッドのシグネチャを変更するとする。新しいパラメータを追加したら、突然50個のテストが通らなくなった。この結果は間違いなく我々に変更を促すものではない。

Goal: Robust Test

プロジェクトの規模が大きくなるにつれて、必然的にコードに様々な変更を加えたくなる。
そのため、1つの変更によって影響を受けるテストの数が非常に少なくなるようにしたい。つまり、テストの重複を最小限に抑える必要がある。また、テスト環境の変更がテストに影響を与えないようにしたい。そのためにはSUTを可能な限り環境から分離したい。

やんやんやんやん

Philosophy of Test Automation

この章では、設計、構築、テストに関する人々の考え方の違いによりどういったパターンを自然に適用するのか変わって来ることを紹介する。

なぜ哲学は大事なのか

テスト観は、自動テストの進め方に大きな影響を及ぼす。たとえばモックをなるべく使いたくない人もいれば積極的に使う人もいる。こうした議論の中から、「テストを書くのは後か先か」「テストは1回ずつか一斉か」といった哲学的な違いが生まれてきた。

いくつかの哲学的な違い

テストは先か後か

従来の開発手法では一通り設計と開発をしたあとにテストを書くが、アジャイルな手法ではテストから書き始める。なぜテストと開発の順番が重要なのだろうか?

完成されたプロダクションコードに後からテストを付け足すのは難しい。テストしやすい設計になっていないとテストが書きづらく、プロダクションコードにも修正を加える必要が出てくる。しかし、テストを先に書けば本質的にプロダクションコードがテスト可能なものになる。

テストかサンプルか

ソフトウェアが書かれる前に自動テストを書くという話に対して「まだ存在しないもののテストをどうやって書くんだ?」と疑問を持つ人もいる。そういうとき、著者はEDD (例駆動開発) の話をする。テストではなくサンプルであれば、ソフトウェア開発の前に書かれることが想像しやすい人もいる。例が実行可能であり、要件が満たされているかどうかを明らかにするのは後の議論のために残しておくことができる。

test-by-test or test all-at-once

TDDでは、テストを先に書いて、テストを通るようにコードを書く。そしてリファクタリングするというサイクルを細かく回す。これが唯一の方法かと言われるとそういうわけではない。
あらかじめすべてのテストを書きたい人もいる。
TDD純粋主義の人は「一度に1つのテストのことだけ考えるようにすれば、集中力を維持するのは簡単だ」という。
よりきめ細かいインクリメンタルな開発により、テストによる欠陥の特定が容易であり、最後にどんな変更をしたのかが鮮明に記憶に残る。

Outsite-In or Inside-Out

ソフトウェアを外から内に向かって設計するということは、システムの全体から考えてそこから各コンポーネントを考えるということだ。テストでいうとまずブラックボックス的なストーリーテストから考え、そこからユニットテストを考える。このながれは、開発者にクライアント視点で考えることを促す。
依存性の問題を防ぐために、Outside-Inで設計し、Inside-Outで開発することを好む人もいる。この場合、内側のテストを書く際に外側のソフトウェアの必要性を予測する必要がある。
内側のテストは、より外側のコンポーネントがすでに構築されていることを利用する。これによりテストスタブを利用する必要はなくなる。(本物を使えばいい)

State or Behavior Verification?

コードをOutside-In で書くことから、状態だけでなく、振る舞いを検証することは小さなステップに過ぎない。統計主義的な考え方では、SUTを特定の状態にし、それを実行しテスト終了時に期待した状態にあることを検証すれば十分であるとしている。
一方振る舞い主義な考え方では、SUTの開始状態と終了状態だけでなく、依存関係に対して行う呼び出しも規定スべきであると述べている。SUTのそうしたインターフェースに対する出力は、クライアントやテスト側からはわからないため特別なことをする必要がある。

Fixture Design Upfront or Test-by-Test?

従来のテストコミュニティでは、アプリケーションといろいろなテストデータがすでに登録されているデータベースからなる「テストベッド」を定義するのが一般的なアプローチだった。ただ、あらかじめ様々はテストケースに対するフィクスチャを用意することは、どのテストのためのフィクスチャ7なのかが読む側からしたらわかりづらい。
そのため、各テストメソッドに対してminimal fixtureを用意することで、前もって大きなフィクスチャの設計をする必要はなくなる。

When Philosophies Differ

他人の哲学が異なっていたとき、自分の哲学を他人に説得するのではなく、他人の哲学が異なることを受け入れ、なぜその哲学をいいものとしているのかを理解しようとすることで自身の考えが深まる。

My Philosophy

著者の哲学

  • テストを書け
    • 1つずつテストを書くのもいいけど、あらかじめテストを側だけリストアップすることもある
  • 主にState Verificationのテストを書くが、場合によってはBehavior Verificationもやる
  • テストごとにフィクスチャ設計をする
やんやんやんやん

Principles of Test Automation

前章で話した自動化テストを書く人々の考え方は、テストを書く際に守る原則の背景となる。
なぜ原則と呼ぶかというと、

  • パターンと呼ぶには抽象的すぎる
  • 誰もが共有できるわけではない (つまり、銀の弾丸ではない)

The Principles

著者はShaun Smith と自分たちにとっての自動化テストを書く際の原則をリストアップした。
原則はパターンよりも規定的であり、より高いレベルである。パターンとは異なり代替案はなく、「なぜならこうする」という形で提示される。命令形の命名がされている。ほとんどの場合ユニットテスト、ストーリーテスト両方に適用できる。

Principle: Write the Tests First

TDD, テストファースト開発とも呼ばれる。
ユニットテストはデバッグの労力を大幅に削減する。コードを書く前にテストを書くことで、テストしやすい設計にすることを強制できる。つまり、テストを書くことでテストしやすいコードを設計できる。

Principle: Design for Testability

もしテスト容易性が担保されていなければ、後からテストを書くことはできないだろう。

Principle: Use the Front Door First

オブジェクトにはいくつかのインターフェースがある。クライアントが使うためのパブリックなものから、モジュール内に閉じたプライベートなものもある。使用するインターフェースの種類はテストの堅牢性に影響する。バックドア操作を使ってフィクスチャのセットアップなどをすると過密結合ソフトウェアになり、テストのメンテが頻繁に必要になる。挙動検証やモックを使いすぎると、オーバースペックソフトウェアやもろいテストになり、望ましいリファクタリングができなくなる。すべての選択肢が有効な場合は、ラウンドトリップテストを使うのがよい。
オブジェクトを公開インターフェースを使ってテストし、状態検証を使用して正しく動作したかどうかを 判断する。

Principle: Communicate Intent

自動化されたテストはプログラムなので、構文的に正しくある必要がある。が、テストメンテナの存在を無視してはいけない。多くのコードや条件付きのテストロジックを含むテストはたいてい「曖昧なテスト」になりがち。なぜなら、テストの全体像を詳細から推測しなければならないからだ。テストは「意図を伝える」ことでより理解しやすくより保守しやすくなる。テストユーティリティメソッドをIntent-Revaling Namesで呼び出してフィクスチャをセットアップし、期待する結果が実現されたことを確認する。
テストの中で、どの入力がどの出力になるのかはただちにわかるようになっていなければならない。

Principle: Don't Modify the SUT

効果的なテストをやるには、アプリケーションの一部をTest Doubleに置き換えたり、テスト用のサブクラスを用意したりする必要があることがある。これは、間接的な入力を制御するとか、出力をインターセプトして動作検証する必要があるためだろう。
SUTを変更するのは、いかなる場合でもきけんである。SUTが依存するものを置き換えたつもりが、SUTの一部を置き換えてしまわぬようにしなければならない。

Principle: Keep Tests Independent

手動テストを行うとき、1つのテストでSUTの多くのことを検証しようとするのが一般的である。
このような集約がなぜ起こるのかと言うと、あるテストのためにシステムの開始状態を規定するステップが他の部分の検証にしようするステップの繰り返しになっているだけかもしれないからだ。
あるテストに依存されたテストが失敗すると、依存している側のテストも当然失敗する。両方のテストが失敗した場合、どこが原因になっているのか人間が判別するのは困難である。
独立したテストは、それ単体で実行することができる。独自のフレッシュフィクスチャを用意し、SUTをテストしている振る舞いを検証できるようにしておく。

Principle: Isolate the SUT

ソフトウェアが別の時間とともに変化する他のソフトウェアに依存する場合、そのソフトウェアの変化によってテストが失敗することがあるかもしれない。この問題は Context Sensitivityと呼ばれ、Fragile Testの一種である。我々が振る舞いをコントロールできない部分にソフトウェアが依存しているとき、我々のソフトウェアすべてをテストするのは困難であると感じるかもしれない。これを防ぐには、SUTとテスト不可能なコンポーネントを可能な限り分離する必要がある。

Principle: Minimize Test Overlap

殆どのアプリケーションには検証したい機能がたくさんあるため、すべての機能を正しくテストすることは不可能である。したがって、書くべきテストを選択することはリスク管理の訓練になる。特定の機能に依存するテストをできる限り少なくするようにテストを構成する必要がある。
同じ機能を検証するテストは、たいてい同じタイミングで失敗するし、同じようにメンテする必要がある。もしいくつかの異なるテストで1つのコードを検証する価値があると思った場合、それは異なるテスト条件が見つかったことになる。

Principle: Minimize Untestable Code

コードにはテストしにくいものがある。GUIコンポーネント、テストコード自身などだ。テスト不可能なコードは、安全にリファクタリングすることがむずかしくなり、修正や機能追加することが危険になる。
テスト不可能なコードを最小限にすることで、具体的なアプローチとしては、テストカバレッジが増加する。テスト不可能なクラスからテストしたい部分を移動させればよい。

Principle: Keep Test Logic Out of Production Code

プロダクションコードがテストしやすい設計になっていない場合、テストをしやすいように「フック」をつけたくなることがある。これは、テスト固有のロジックをif分で実行させたりする。
テストとは、システムの挙動を検証することだ。テスト時のシステムの挙動が違ったら、本番のコードがちゃんと動くのかどうかをどうやって検証するのだろう?
つまり、プロダクションコードにテストロジックやテストのための条件分岐はあってはならない。

Principle: Verify One Condition per Test

多くのテストは開始状態にSUTのデフォルト以外の状態を要求し、別の状態にした状態で終わる。
そのため、効率化のために複数のテスト条件の検証を1つにまとめることでテスト条件の再利用を図ろうとすることが望まれている。が、それは推奨できない。
なぜなら、1つのアサーションが失敗すると以降のテストが実行されないからだ。自動テストでは、1つのステップが失敗するとそれ以降のデータを提供することができない。各テストは単一のテスト条件を検証する必要がある。
複数のテストで同じフィクスチャが必要になる場合は、テストケースクラスとしてまとめて、暗黙のセットアップを用意するか、ユーティリティメソッドを用意してセットアップを委譲する。

Principle: Ensure Commensurate Effort and Responsibility

テストを書いたり修正する労力が対応する機能を実装するのにかかる労力を超えてはならない。また、テストに利用するツールは、その機能を実装するのに利用するツール以上の知識を必要としないようにする。
例えば、メタデータを使用してSUTの動作を設定できるとき、メタデータが正しく設定されていることを検証するテストを書きたい場合、そのためのコードを書く必要がないようにしたい。

やんやんやんやん

xUnit Basics

xUnitという用語は、スクリプトによるテストの自動化に使用されるテスト自動化フレームワークのうち、この章で説明する一連の機能を共有するものを指す。

Common Features

xUnitファミリーのほとんどはオブジェクト指向言語で実装されているので、まずはそれらについて説明する。
xUnitファミリーのすべてのメンバーは、基本的な機能セットを実装している。これらは以下のタスクを実行するための方法を提供する。

  • テストをテストメソッドとして指定する
  • 期待される結果をアサーションの呼び出しという形で指定する
  • テストスイートの中のテストの結果を単一の操作の結果として集約できる
  • 1つ以上のテストを実行し、その結果をレポートとして取得する

The Bare Minimum

xUnitがどのように動作するか、最低限理解する。

  • テストケースクラスでテストメソッドを利用してテストを定義する方法
  • 任意のスイートを構築する方法
  • テストを実行する方法
  • テストの結果の解釈方法

Defining tests

各テストは以下の手順で4つのステップから実装される。

  • テストフィクスチャのセットアップ
  • SUTのメソッドを実行する
  • アサーションメソッドを呼び出し、期待された出力かどうかを検証する
  • ティアダウンによるフィクスチャの削除

一般的なテストは、有効な入力に対してSUTが正しく動作することを検証する単純成功テストと、不正に使用された場合に例外がでることを検証する期待例外テストである。
テストメソッドはどこかに保存される必要があるため、テストケースクラスのメソッドとして定義する。

What's a Fixture?

テストフィクスチャは、SUTを実行するために必要なものすべて。この本ではTestCaseクラスとそれが生成するテストフィクスチャを別の命名で表記している。xUnitファミリーの中にはテストケースクラスをフィクスチャと呼ぶものもいる。

Defining Suites of Tests

殆どのテストランナーはテストケースクラスのすべてのテストメソッドを含むテストスイートを自動生成している。アプリケーション全体をテストしたいこともあれば、特定の機能の部分だけをテストしたいこともある。xUnitファミリーのメンバーは、テストランナーがファイルシステムor実行ファイルからテストスイートを検索しテストスイートを見つける機能を持っている。これがない場合はテストスイートの列挙をする必要がある。
この機能では、アプリケーション全体のテストスイートを、いくつかの小さなテストスイートの集合体として定義する。このような階層的な構成は、開発者が興味のあるテストのサブセットを実行するのに実用的である。

Running Tests

どうでもいいので省略

Test Results

自動テストを実行する主な理由は、その結果を判断すること。結果を意味のあるものにするには、それを記述するための標準的な方法が必要。xUnitファミリーは、Hollywood原則にしたがっています。「No news is good news」つまり、問題が発生したらテストが知らせに来る。テストの失敗に焦点を当てているのである。
テスト結果は3つに分類される。
まずテストがエラーや失敗なしに実行されたとき、それは成功とみなされ、xUnitは何も特別なことはしない。テストの失敗は、アサーションに失敗したときである。つまり、テストはアサーションメソッドを呼び出すことで何かが真であることを主張するが、それが事実ではないことが判明する。
SUTまたはテスト自体が予期せぬ方法で失敗した場合はエラーとみなされる。

Under the xUnit Covers

xUnitファミリーのほとんどでは、各テストメソッドは実行時にテストケースオブジェクトとして表現される。テストケースオブジェクトはテストスイートオブジェクトに集約され単一のユーザーアクションで多くのテストを実行できる。

Test Commands

テストランナーは各テストメソッドを個別に呼び出す方法を知ることができない。これを避けるべく、各テストメソッドをrunメソッドを持つコマンドオブジェクトに変換している。テストランナーはテストケースクラスのsuiteメソッドを呼び出してテストスイートオブジェクトを取得する。そして各テストケースのrunメソッドを呼び出す。

Test Suite Objects

テストスイートオブジェクトはすべてのテストケースオブジェクトが実装するのと同じ標準テストインターフェースを実装するコンポジットオブジェクトである. (つまり、テストスイートとテストケースを同一視できるということ) そのインターフェースはrunメソッドを実装することを要求する。runを実行するとReceiverに含まれるすべてのテストが実行される。テストケースの場合自身のテストを、テストスイートの場合は自身が持つテストケースすべてのテストを実行することを意味する。

xUnit in the Procudural World

やんやんやんやん

Transient Fixture Management

この章では選択したフィクスチャ戦略を実装するための仕組みに焦点を当てる。フレッシュフィクスチャをどう書くかは、テストにかかる労力やドキュメントとしてのテストを実現できるかに影響する。

What is a Fixture?

すべてのテストは4つの段階で構成されている。
そのはじめの部分では、 SUTとそれに依存するものすべてを作成し、それらの要素をSUTを実行するために必要な状態にする。xUnitでは、SUTを実行するために必要なものすべてをテストフィクスチャと呼び、それをセットアップするために実行するテストロジックをフィクスチャセットアップと呼ぶ。
フィクスチャセットアップの一般的な方法はフロントドアのフィクスチャセットアップを使用すること。つまり、SUT上のメソッドを利用して開始状態まで持っていく。
SUTの状態が他のコンポーネントに格納されている場合、バックドアセットアップを行うこともある。つまり、SUTの動作が依存する他のコンポーネントに必要な情報を直接操作する。

What is a Fresh Fixture?

フレッシュフィクスチャ戦略では、テストを実行するたびに新たなフィクスチャをセットアップする。つまり、各テストケースオブジェクトがSUTを実行する前に独自のフィクスチャを構築する。

What is a Transient Fixture?

フィクスチャがローカル変数等の場合、テストが終わるたびに消滅する。フィクスチャが永続的(DBにデータが入ってるとか)だとそうではない。したがって、フレッシュフィクスチャを実装するかについていくつか決断を迫られる。フレッシュさを維持するために、テストのたびにフィクスチャを破棄するという方法が1つ。もう一つは、新しいフィクスチャを古いものと衝突しないように作ること。永続的なフィクスチャについては9章で扱う。

Building Fresh Fixture

一時的なものでも永続的なものでも、フィクスチャの構築方法の選択肢はほぼ同じである。
フィクスチャ設定ロジックには

  • SUTをインスタンス化するために必要なコード
  • SUTを適切な状態にするコード

フィクスチャをセットアップする明快な方法はインラインセットアップであり、テストメソッドの中にロジックが含まれる。Delegated Setupで構築もできる、この場合はTest Utility Methodsを呼び出すことになる。

Inline Fixture Setup

Inline Setupでは、テストメソッド内ですべてのフィクスチャのセットアップをする。オブジェクトを構築し、それに対してメソッドを呼び出してSUTを構築する。そしてSUTに対してメソッドを呼び出して特定の状態にする。これらのタスクがすべてテストメソッドから呼び出される。

 public  void  testStatus_initial()  {
      //  In-line  setup
      Airport  departureAirport  =  new  Airport("Calgary",  "YYC");
      Airport  destinationAirport  =  new  Airport("Toronto",  "YYZ");
      Flight  flight  =  new  Flight  (  flightNumber,
                                                   departureAirport,
                                                   destinationAirport);
      //  Exercise  SUT  and  verify  outcome
     assertEquals(FlightState.PROPOSED,  flight.getStatus());
      //  tearDown:
         //       Garbage-collected
}

Inline Setupのデメリットは、すべてのテストでSUTの構築をするため、テストコードが重複することだ。また、多くのテストメソッドは似たようなフィクスチャのセットアップを実行する必要がある。こうしたテストコードの重複はFlagile Testによる高いメンテコストを引き起こす。Inline Setupでは各テストメソッド内でハードコードされたテストデータを推奨する傾向がある。
フィクスチャのセットアップをするコードをテストメソッドの外に出すことでこれらの匂いを防ぐことができる。

Delegated Fixture Setup

テストコードの重複を減らし、不明瞭なテストを減らすための簡単な方法はDelegated Setupを使うようにリファクタリングしていくことだ。Extract Methodリファクタリングによって、いくつかのテストメソッドで使われているステートメントをユーティリティメソッドに移動し、テストメソッドから呼び出す。抽出されたメソッドにテストが依存するオブジェクトを作成するロジックが含まれている場合、それをCreation Methodと呼ぶ。明示的な名前をCreation Methodに持たせることで、テストの前提条件をわかりやすくし、不要なテストコードの重複を回避する。

 public  void  testGetStatus_inital()  {
       //  Setup
      Flight  flight  =  createAnonymousFlight();
      //  Exercise  SUT  and  verify  outcome
     assertEquals(FlightState.PROPOSED,  flight.getStatus());
      //  Teardown
      //   Garbage-collected
} 

Creation Methodの目的の1つは、各テストが必要とするオブジェクトの作成方法の詳細を知らなくても良い状態にすること。コンストラクタが修正されたとしてもFlagile testを防ぐことができる。
テストがオブジェクトの作成方法を気にしない場合、Anonymous Creation Methodを使用する。これは作成するオブジェクトが必要とする一意のキーを生成する。これにより他のテストインスタンスが同じオブジェクトを使用しないよう保証できる。テストがオブジェクトの属性を気にする場合、パラメータつきのAnonymous Creation Methodをつかう。