テスト駆動開発 (TDD) とは
テスト駆動開発(Test-Driven Development; TDD)は、
ソフトウェア開発手法の一つで、プログラムの各機能に対して、まずテストコードを記述し、そのテストをパスするような実装を行うことを繰り返す開発スタイルです。これは「テストファースト」とも呼ばれ、アジャイル開発、特にエクストリーム
プログラミングで強く推奨されています。
TDD の開発サイクル
TDD の基本的な開発サイクルは以下の通りです。
1.
失敗するテストを書く:まず、実装したい機能に対するテストコードを作成します。この段階では、まだコードが存在しないため、テストは必ず失敗します。
2.
テストをパスする最小限のコードを書く:テストが失敗することを確認したら、次に、テストをパスするために必要な最小限のコードを実装します。この段階では、コードの品質は問わず、テストをパスすることを最優先します。
3.
コードをリファクタリングする:テストがパスしたら、コードの重複を削除したり、より読みやすいコードに改善したりするリファクタリングを行います。
このサイクルは、テスト実行環境のツールである xUnit において、テストの失敗を赤いバー、成功を緑のバーで示すことから、Red/Green/Refactor とも呼ばれます。
実践的な TDD の手順
より実践的な TDD では、To-Do リストを組み合わせて開発を進めます。
1.
テスト項目の列挙:まず、現時点で必要なテスト項目をリストアップします。新しいテストの必要性がわかった時点で、随時項目を追加します。
2.
項目の選択:リストから、実装可能で重要な項目を選択します。テスト記述が容易でも、すぐに実装できない場合は後回しにします。実装できそうな項目がない場合は、項目の粒度が大きすぎる可能性があるので、より小さい粒度の項目に分割してリストに追加します。
3.
テストの作成:選択した項目に対してテストを作成します。現在の実装では必ず失敗するテストを記述します。
4.
テストの失敗を確認:テストを実行し、失敗することを確認します。もしテストが予期せず成功した場合は、テストケースを見直します。
5.
コードの実装:テストをパスするために必要な最小限のコードを実装します。この段階では、どんな手段を使ってもテストをパスさせることを優先します。実装方法としては、以下の 3 つがあります。
自明な実装 (Obvious Implementation):1 分程度で実装できる場合は、直接コードを記述します。
仮実装 (Fake It):テストが要求する値をハードコーディングします。
三角測量 (Triangulate):仮実装後に、別のデータを使ったテストを追加し、共通点を見つけ出してロジックを抽出します。
6. リファクタリング:テストがパスすることを確認しながら、コードの重複を取り除き、コードの品質を改善します。リファクタリングとは、コードの意味を変えずにコードを再構築することであり、テストがパスすることがその基準となります。また、ハードコーディングした値を、本来の値が得られる計算ロジックに置き換えるなど、意味的な重複も取り除きます。
7. 項目の削除:実装済みの項目をリストから削除します。
テストコードは最初から完璧である必要はありません。実装と同様に、最初は具体的なテストから始め、知見を得てから書き直すこともあります。また、テストコードから導かれるコード本体は、リファクタリングの過程でテスト用のスタブに変化することもあります。テストとコード本体は、成熟するにつれて抽象的になり、テストコードにリスクが導入されることもありますが、開発者の正しさへの確信を裏付けるためのテストであるという目的が保たれていれば問題ありません。
TDD で用いられるテストは、品質を保証するためのものではなく、コードの正しさを開発者が確信するのを助けるためのものです。したがって、開発者の確信に寄与しないテストや、ドキュメントとしての価値がないテストは削除を検討するべきです。
TDD の実行環境
TDD を実施するには、テストを自動的に実行できる環境が必要です。JUnit や NUnit などの xUnit フレームワークがよく利用されます。テスト実行環境自体を TDD で自作することも良い練習になりますが、その場合は、テストツールをテストするツールが存在しないため、しばらくの間は人の判断によってテストの代わりとする必要があります。
TDD の利点
TDD の目標は、Clean code that works(動作するきれいなコード)を作成することです。
手戻りの削減:テストをパスすることで、コードの品質が保証され、手戻りを減らすことができます。実装方法が見当つかない場合でも、コードを書きながら具体化していくことができます。
フィードバックの迅速化:設計や実装の決定に対するフィードバックを、テストコードやテストの成否を通して、早い段階で得ることができます。
高いテストカバレッジ:コードはテストをパスするために記述されるため、理想的な TDD では、テストカバレッジは 100% になり、欠陥や
バグが少ないことが期待できます。
適切な抽象化:具体的なコードから始めて、徐々に抽象化を進めることで、過度な抽象化を防ぎ、保守性の高いコードを生成することができます。
TDD の問題点
適用の注意点
TDD が難しいとされるケースもあります。
セキュリティソフトウェア:テストで盲目的に判断できないため、人の判断が必要となります。
並列処理・並行処理:非決定論的な要素が絡み、再現性に問題があるためです。
GUI を扱うもの、分散オブジェクト、データベーススキーマ、サードパーティコードや外部ツールで自動生成されるコード:これらも、TDD でテストするのが難しいとされています。
また、既に構築されたコードに TDD を導入する場合も、テストがしやすいようにコードをリファクタリングする必要があるため、難易度が高い場合があります。重要なのは、変更範囲を限定し、フィードバックを得ながら進めていくことです。
まとめ
TDD は、テストを先に書くことで、より品質の高いコードを効率的に開発するための強力な手法です。開発プロセスに取り入れることで、手戻りの削減、迅速なフィードバック、高いテストカバレッジなど、多くのメリットを得ることができます。しかし、適用が難しいケースもあるため、状況に応じて最適な開発手法を選択することが重要です。