テスト駆動開発
テスト駆動開発 (てすとくどうかいはつ、test-driven development; TDD) とは、プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと言う)、そのテストが動作する必要最低限な実装をとりあえず行った後、コードを洗練させる、という短い工程を繰り返すスタイルである。多くのアジャイルソフトウェア開発手法、例えばエクストリーム・プログラミングにおいて強く推奨されている。近年はビヘイビア駆動開発へと発展を遂げている。
目次 |
[編集] 開発サイクル
最も基本となる開発サイクルは以下のようになる。
- 失敗するテストを書く
- できる限り早く、テストがパスするような最小限のコード本体を書く
- コードの重複を除去する(リファクタリング)
テストの実行環境ツールであるxUnitでは、テストの失敗を赤いバー、成功を緑のバーで通知するため、上記のサイクルは Red/Green/Refactor と称される。
より実践的には、to-doリストの利用を組み合わせた以下の手順で開発を行う。
- まず、現時点で分かっている範囲でテストの必要性がある項目をリストとして列挙する。このリストは適時、テストの必要性がわかった時点でその項目を追加していく。
- このリストから1つ選ぶ。これは、実装できそうなものでしかしトリビアルでないものを選ぶ(テスト自体の記述が容易でも、Fake It(後述)でしかコード本体を記述できなさそうなものは後回しにする)。実装できそうなものが無い場合は、列挙した項目の粒度が大きすぎることを意味する。その場合、それを実装するための前提にできるような、より小さい粒度の項目を作りリストに加える。
- 選択した項目についてテストを記述する。このテストは、現在の実装を用いると失敗するようなものを選ぶ。
- コンパイルに必要な最小限のコード(例えば、まだ存在しないクラス・メソッドを利用するテストを書いたなら、そのクラス・メソッドの宣言)の追加後、実際にテストを実行し失敗することを確認する。期せずしてテストがパスした場合は、意図しないことが起こっていることに注意する。テストが失敗するまでは、コード本体は触らない。
- できる限り早くテストの失敗を解消するようにコード本体を記述する。この段階では、テストをパスさせるためにどんなことをしても良い(定数を返す、コピー&ペースト、コードの重複等)。具体的には3つの方法が挙げられる。
- 実装が自明な場合(1分程度で書ける場合)はそれを記述する(Obvious Implementation)。
- テストに要求される値そのものをハードコーディングする(Fake It;仮実装)。
- Fake Itの後に次のリファクタリングの段階へ進めないような漠然としている場合は、さらに別のデータを用いたテストを追加し、その2つのテストの共通点を見出して助けとする(Triangulate;三角測量)。
- テストがパスすることを保持しつつ、コード本体やそれとテストの間の明示的・暗黙的重複を取り除く(リファクタリング)。通常、リファクタリングとはコードの意味を変えずに再構築することを言うが、ここでは、「コードの意味」はテストが通ることを言う。リファクタリングで取り除く対象となる重複は、形式的なものだけではなく意味的なものも含む。例えば、Fake Itでハードコーディングしたものはおおよそ、実際にはどこからか得られるはずのパラメータの値を、それとは別に値を用いて算出したものであり、これを重複と見なす。重複を取り除くことでロジックが抽出される。
- リストから実装した項目を削除する。作業中にテストの必要性が判明した他の項目に、実質的に振り変わるかもしれない。
テストコードは最初から自明であるとは限らない。むしろ、コード本体と同様、最初は具象的なテスト(例えば単なるフラグの確認)を行い、これにて知見を得た後にテストを書き直して良い。また、テストコードから導かれるコード本体は、リファクタリングの過程により、あるいはテストが成熟するに従い、最終的な目的とするコード本体のテスト用スタブに変わっていくかもしれない。早い段階でテストとコード本体を分離して管理するのはあまり意味が無い。テストやコード本体が成熟していくにつれ、テストの記述が抽象的・間接的になり、リスクが導入される(例えば、フィールドを直接参照する代わりにgetterメソッドを使うなど)。しかし、テスト駆動開発のテストの目的は、開発者の正しさへの確信を裏づけするためであり、それが保たれているならば問題はない。
テスト駆動開発で用いられるテストは、品質のためのテストではない。コード本体とは独立にあらゆるケースを網羅するような、テスト自体で価値を持つようなものは目指していない。コード本体を合わせて検討することで、開発者が、その正しさに確信を得るものである。開発者の確信に少しも寄与しないテスト(かつ、ドキュメントとしてテストの読者に何かを伝えるために書かれたものではないもの)は、むしろ積極的に削除を検討する。
テスト駆動開発を実施するには、テストを自動的に実行できる環境が必要である。そのような環境としては、JUnitやNUnitといったもの(総称してxUnitとされる)が挙げられる。なお、このテスト実行環境は、コンセプトが簡単なわりに非常に強力なツールであることから、新しい言語の習得を兼ねてそれ自体をテスト駆動開発で自作するのもよい。ただし、そのテストツールをテストするツールは無いことから、しばらくは慎重な人の判断でもってテストの代わりとすることになる。
[編集] 利点
- "Clean code that works." がテスト駆動開発の目標である。クリーンでかつ動くという2つのことを同時に考えるのは、場合により非常に困難なものになりうる。このためテスト駆動開発では、まず動く(テストをパスする)コードを作ることに専念し、次にこれをクリーンにする(リファクタリング)ことに専念することで、これを解決する。
- テストのパスにより、進捗が後戻りしないことを確認できる。逆に、Fake ItやTriangulateなど、実装するものの検討がつかなくとも、頭で考え込む代わりに前にコードで具体化して進むことができる(そして、それは実際には考える必要のないものであることが後で分かるかもしれない)。テスト駆動開発は着実に進捗を進めることを可能にする開発方法である。
- 設計に関する決定のフィードバックとして、それを具体的にテストコードに記述することによりその良し悪しが判断できる。実装に関する決定のフィードバックは、テストの失敗やパスによって通知される。両者とも採用の決定から短い間隔でフィードバックを得ることができる。
- テスト駆動開発はあくまで開発のための手法であり、そこで得られるテストは副産物であるが、その開発手順からコード本体はテストをパスさせるために記述されたものであるため、理想的なテスト駆動であればカバレージは100%になる。欠陥やバグは非常に少ないことが期待される。
- 具象的なコードから始まり抽象化するため、適切な抽象化で抑えられる。過度な抽象化はむしろ柔軟性を失わせ、保守のコストもかかる。現在の仕様に対応するだけのシンプルさを保つことで、逆説的に将来の仕様変更への対応が容易になる。
[編集] 問題点
[編集] 適用の注意
テスト駆動開発に適合させるのが難しいものに、以下のものが挙げられる。
- セキュリティソフトウェア
- 並列処理
前者は、盲目的にその良し悪しが判断できない(人の判断が必要である)こと、後者は、再現性に問題があるためやはり盲目的に良し悪しを判断できないためである。不具合がないことは前提であるため、その点に関してはテスト駆動の効果はある。
以下のものもテスト駆動開発が難しいとの指摘がある (Darach's Challenge)
- グラフィカルユーザインターフェース (GUI) を扱うもの
- 分散オブジェクト
- データベーススキーマ
- サードパーティコードや、外部ツールで自動生成されるコード
既に構築してあるコードに対して、テスト駆動開発を導入するのも難しい。それらのコードは得てしてテストがしやすいようには作られていないため、先にリファクタリングをする必要があるが、リファクタリングを行うにはその動作の保証をするためのテストが必要である(デッドロック)。やってはいけないことは、片っ端からテストを書いたり、リファクタリングを行うことである。変更する範囲を制限することを決心し、何らかのフィードバックを得ることを前提として(例えば、ペアリングで注意深く作業を行う、アプリケーションレベルのテストなど)コードを変更していくことが必要である。
[編集] 参考文献
- Beck, Kent (2002). Test-Driven Development: By Example. Addison-Wesley Professional. pp. 240. ISBN 0321146530.
- ケント・ベック 『テスト駆動開発入門』 長瀬嘉秀監訳、(株)テクノロジックアート訳、ピアソン・エデュケーション、2003年。ISBN 4894717115。
[編集] 関連項目
[編集] 外部リンク
- 特集「テスト駆動開発」はプログラマのストレスを軽減するか?(@IT): テストファーストとテスト駆動開発との違い、テスト駆動開発の具体的な手法について書かれている。