テスト駆動開発

テスト駆動開発 (TDD) は、継続的な設計の要素を追加したテストファースト プログラミングの特殊なケースです。

テストファーストプログラミングでは、製品コード用の自動ユニットテストを作成し、   製品コードを作成します。後からテストを書くのではなく (または、より一般的には、テストをまったく書かない)、常にユニット テストから始めます。製品コードのすべての機能の小さなチャンクに対して、最初に、コードが行うことを指定および検証する小さな (理想的には非常に小さな) 焦点を絞ったテストを構築して実行します。このテストは、必要なクラスとメソッドがすべて存在するとは限らないため、最初はコンパイルされない可能性があります。それでも、一種の実行可能な仕様として機能します。次に、最小限の製品コードでコンパイルして、実行して失敗を確認します (失敗すると予想したものが成功することもあり、これは役立つ情報です)。次に、そのテストを成功させるために必要なだけのコードを作成します。

この手法は、多くのプログラマーにとって最初は奇妙に感じられるでしょう。まるでロッククライマーが岩壁をゆっくりと登りながら、アンカーを打ち込んでいくようなものです。なぜこんな面倒なことをするのでしょうか? きっとかなり遅くなるのではないでしょうか? 答えは、後々ユニットテストに何度も依存することになる場合にのみ意味があるということです。テストファーストを日常的に実践している人たちは、ユニットテストは作成に要した労力を十分に回収できると主張しています。

テストファーストの作業では、通常、xUnitファミリーの自動ユニットテストフレームワーク(Javaの場合はJUnit、C#の場合はNUnitなど)のいずれかを使用します。これらのフレームワークを使用すると、大規模なユニットテストスイートの作成、実行、整理、管理が非常に簡単になります(少なくともJavaの世界では、これらのフレームワークは優れたIDEにますます統合されています)。これは良いことです。なぜなら、テストファーストで作業を進めると、膨大な数のユニットテストが蓄積されるからです。

テストファースト作業の利点

徹底した自動化されたユニットテストは、バグ検出のための一種の網として機能します。システムの現在の動作を正確かつ決定論的に特定します。優れたテストファーストのチームは、システムのライフサイクル全体を通して欠陥が大幅に減少し、デバッグにかかる​​時間も大幅に短縮されることに気づいています。よく書かれたユニットテストは、定義上、常に製品版のコードと同期した優れた設計ドキュメントとしても機能します。やや意外な利点として、多くのプログラマーが、すべてのテストが正常に実行されていることを示す「小さな緑色のバー」が病みつきになると報告しています。コードの健全性に関するこのような小さくて頻繁な肯定的なフィードバックに慣れてしまうと、それを手放すのは非常に困難です。最後に、コードの動作が多くの優れたユニットテストによって特定されていれば、 safeあなたのために コードをリファクタリングするリファクタリング(またはパフォーマンスの調整、その他の変更)によってバグが発生した場合、テストによってすぐに警告が出されます。

テスト駆動開発:さらに進化させる

テスト駆動開発 (TDD) は、継続的設計の要素を加えたテストファースト プログラミングの特殊なケースです。TDD では、システム設計は紙の設計ドキュメントに制約されません。代わりに、テストと製品コードの作成プロセスによって、設計を進めていきます。数分ごとに、簡素化と明確化のためにリファクタリングを行います。より明確で簡潔なメソッド、クラス、またはオブジェクト モデル全体を容易に想像できる場合は、その方向にリファクタリングします。その間、堅牢な単体テスト スイートによって保護されます。TDD の根底にある前提は、コードに腕を深く突っ込んでみるまで、どの設計が最適であるかを本当に判断することはできないということです。実際に機能するものと機能しないものについて学習していくうちに、その洞察が記憶に新しいうちに、それを適用できる最適な立場に立つことができます。そして、このすべてのアクティビティは、自動化された単体テスト スイートによって保護されます。

かなりの初期設計から始めることもできますが、かなり初期設計から始めるのが一般的です。 シンプルなコード設計エクストリームプログラミングの世界では、ホワイトボードに描いたUMLスケッチで十分な場合が多い。しかし、TDDにおいては、どの程度の設計から始めるかよりも、設計を進める中でどれだけ出発点から逸脱できるかが重要だ。アーキテクチャを抜本的に変更する必要はないかもしれないが、それが最も賢明な方法だと思われる場合は、オブジェクトモデルを大幅にリファクタリングすることもある。真のTDDを実装する上で、組織によっては政治的な裁量が大きい場合もある。

テストファースト vs. デバッグ

テスト作成に費やした労力とデバッグに費やした時間を比較してみると分かりやすいでしょう。デバッグは多くの場合、膨大な量のコードに目を通す作業です。テストファーストの作業では、一口サイズのコードに集中できるため、問題が発生する可能性が低くなります。管理者がデバッグに実際にどれくらいの時間がかかるかを予測するのは困難です。そしてある意味、多くのデバッグ作業が無駄になっていると言えるでしょう。デバッグには、時間の投資、足場作り、そして本質的に使い捨てのインフラストラクチャ(ブレークポイント、一時変数の監視、print文)が含まれます。バグを見つけて修正すると、その分析データはすべて実質的に失われます。そして、あなた自身が完全に失わなくても、そのコードを保守または拡張する他のプログラマーにとっては確実に失われます。テストファーストの作業では、テストは誰もが永久に利用できます。何らかの理由でバグが再発した場合、一度検出した同じテストで再び検出できます。対応するテストが存在しないためにバグが発生した場合は、それ以降はそれを捕捉するテストを作成できます。このように、多くのテストファースト実践者は、テストファーストこそが、より努力するのではなく、より賢く働くことの典型であると主張しています。

テストファーストの手法とツール

システムの動作のあらゆる側面に対して単体テストを書くのは、必ずしも簡単ではありません。GUI はどうでしょうか。EJB や、コンテナベースのフレームワークによって管理されている他の生き物はどうでしょうか。データベースや永続性全般はどうでしょうか。例外が適切にスローされることをどのようにテストするのでしょうか。パフォーマンス レベルをどのようにテストするのでしょうか。テスト カバレッジ、テストの粒度、テストの品質をどのように測定するのでしょうか。これらの質問は、テストファースト コミュニティによって、進化を続けるツールとテクニックのセットによって答えられています。単体テストでシステムの動作のあらゆる側面をカバーできるようにするために、多大な創意工夫が注ぎ込まれ続けています。たとえば、多くの場合、フェイクやモック オブジェクトを使用して、システムのコンポーネントをその連携オブジェクトや外部リソースから切り離してテスト駆動することが理にかなっています。これらのモックやフェイクがないと、単体テストはテスト対象のオブジェクトをインスタンス化できない可能性があります。また、ネットワーク接続、データベース、GUIなどの外部リソースの場合、テストで本物を使用すると動作が大幅に遅くなる可能性がありますが、偽物やモックバージョンを使用するとメモリ内ですべてが高速に実行されます。また、機能の一部には常に 手動テスト、それが間違いなく真実である割合は縮小し続けています。