テスト駆動開発(TDD)とは、自動化された単体レベルのテストケースを作成して失敗させ、次にそのテストが合格するのに十分なだけのコードを作成し、テストコードと本番コードの両方をリファクタリングし、そして別の新しいテストケースでこれを繰り返すというコードの書き方です。
自動テストを作成する別のアプローチとしては、テストコードの作成を開始する前にすべての本番コードを作成するか、本番コードの作成を開始する前にすべてのテストコードを作成する方法があります。TDDでは、両方を同時に作成するため、デバッグに必要な時間を短縮できます。[ 1 ]
TDDは、1999年に始まったエクストリームプログラミングのテストファーストプログラミングの概念に関連しているが[ 2 ]、最近ではそれ自体でより一般的な関心を集めている[ 3 ] 。
プログラマーは、古い技術で開発されたレガシーコードの改善やデバッグ にもこの概念を適用します。[ 4 ]
この手法を開発または「再発見」したとされるソフトウェアエンジニアのケント・ベックは、2003年にTDDはシンプルな設計を促し、自信を育むと述べた。 [ 5 ] [ 6 ]
TDDの原典は、プログラミングに関する古い書籍にありました。そこには、入力テープを用意し、期待する出力テープを手動で入力し、実際の出力テープが期待する出力と一致するまでプログラミングを続ける、と書かれていました。Smalltalkで最初のxUnitフレームワークを書いた後、この記述を思い出し、試してみました。これが私にとってのTDDの原点です。年配のプログラマーにTDDを説明すると、「もちろん。他にどんなプログラミング方法があるんだ?」とよく言われます。そのため、私は自分の役割をTDDの「再発見」と呼んでいます。

TDDのステップは、著者によって数や説明が多少異なりますが、一般的には以下のとおりです。これらは、書籍『Test-Driven Development by Example』[ 6 ]とケント・ベックのCanon TDDの記事[ 8 ]に基づいています。
各テストは小規模にし、コミットは頻繁に行うべきです。新しいコードがテストに失敗した場合でも、プログラマーは過剰なデバッグを行うのではなく、元に戻したり、変更をロールバックしたりすることができます。
外部ライブラリを使用する場合、ライブラリにバグがある、または開発中のソフトウェアのすべてのニーズを満たすのに十分な機能が備わっていないと考える理由がない限り、ライブラリ自体を実質的にテストするだけの小さなテストを書かないことが重要です。[ 3 ]
TDDは、ソフトウェア開発以外の分野でも、製品チームとサービスチームの両方で、テスト駆動開発として採用されています。[ 9 ]テストを成功させるには、ミクロレベルとマクロレベルの両方でテストを実施する必要があります。クラス内のすべてのメソッド、すべての入力データ値、ログメッセージ、エラーコードなど、さまざまなデータポイントをテストする必要があります。[ 10 ] TDDと同様に、ソフトウェア以外のチームも、作業を開始する前に、作業の各側面について品質管理(QC)チェック(通常は自動テストではなく手動テスト)を作成します。これらのQCチェックは、設計に役立て、関連する結果を検証するために使用されます。TDDシーケンスの6つのステップは、意味的に若干の変更を加えて適用されます。
テスト駆動開発にはさまざまな側面があり、たとえば「シンプルに保つ」(KISS)や「必要ない」(YAGNI)の原則などがあります。テストに合格するために必要なコードだけを書くことに集中することで、他の方法で達成されるよりも設計がよりクリーンで明確になることがよくあります。[ 6 ]『Test-Driven Development by Example』の中で、ケント・ベックは「成功するまでふりをする」という原則も提案しています。
デザインパターンなどの高度な設計コンセプトを実現するために、そのデザインを生成するテストが作成されます。コードは目標とするパターンよりもシンプルになる場合もありますが、必要なテストはすべてパスします。最初は戸惑うかもしれませんが、開発者は重要なことだけに集中できるようになります。
テストを先に書く:テストは、テスト対象の機能を作成する前に書くべきです。これには多くの利点があるとされています。開発者は、後から追加するのではなく、最初からアプリケーションのテスト方法を検討する必要があるため、アプリケーションがテストしやすいように作成されていることを保証するのに役立ちます。また、すべての機能のテストが確実に作成されることも保証されます。さらに、テストを先に書くことで、製品要件をより深く、より早く理解することができ、テストコードの有効性が保証され、ソフトウェアの品質に継続的に焦点を当てることができます。[ 11 ]
機能優先のコードを書く場合、開発者や組織は、テストを完全に無視して、開発者を次の機能へと急がせる傾向があります。最初のTDDテストは、必要なクラスやメソッドがまだ存在しないため、最初はコンパイルすらできないかもしれません。それでも、その最初のテストは実行可能な仕様の始まりとして機能します。[ 12 ]
各テストケースは最初は必ず失敗します。これは、テストが実際に機能し、エラーを検出できることを保証するためです。これが確認できたら、基盤となる機能を実装できます。このことから、「テスト駆動開発」のモットーである「赤/緑/リファクタリング」が生まれました。赤は失敗、緑は成功を意味します。テスト駆動開発では、失敗するテストケースを追加し、それをクリアし、リファクタリングするという手順を繰り返します。各段階で期待されるテスト結果を得ることで、開発者のコードに関するメンタルモデルが強化され、自信が高まり、生産性が向上します。
テスト駆動開発では、実装前にテストを作成すると、プライベートメソッドをテストするか、パブリックインターフェースのみをテストするかという問題が生じます。この選択は、テストコードと本番コードの両方の設計に影響を与えます。
テスト駆動開発(TDD)は、迅速なレッド・グリーン・リファクタリングサイクルを実現するために、主に単体テストに依存しています。これらのテストは、プロセス境界、ネットワーク接続、外部依存関係を回避することで、高速に実行されます。TDDの実践者は、コンポーネント間の相互作用を検証するために統合テストも作成しますが、これらの実行速度の遅いテストは、より頻繁に実行される単体テストとは別に管理されます。複数の統合モジュールを一緒にテストすると、障害の原因を特定することがより困難になるという問題もあります。
開発中のコードが外部依存関係に依存している場合、TDD は高速で分離された単体テストを維持するためにテスト ダブルの使用を推奨します。 [ 13 ]一般的なアプローチは、インターフェースを使用して外部依存関係を分離し、テスト目的でテスト ダブルを実装することです。
テストダブルは実際の外部コンポーネントとの関連性を証明できないため、TDDの実践者は適切なレベルでユニットテストに加えて統合テストを実施します。実行速度と信頼性を高めるため、ユニットレベルでのテストを最大化し、より上位レベルでの時間のかかるテストを最小限に抑えます。
TDD(テスト駆動開発)では、ユニットは一般的にクラス、またはモジュールと呼ばれる関連関数のグループとして定義されます。ユニットを比較的小さく保つことで、以下のような重要な利点が得られるとされています。
テスト駆動開発の高度な実践により、受け入れテスト駆動開発(ATDD) や例による仕様作成につながり、顧客が指定した基準が受け入れテストに自動化され、それが従来の単体テスト駆動開発 (UTDD) プロセスを推進します。[ 14 ]このプロセスにより、顧客はソフトウェアが要件を満たしているかどうかを判断する自動化されたメカニズムを持つことができます。ATDD では、開発チームは受け入れテストという特定の目標を満たすことができるため、各ユーザー ストーリーから顧客が本当に求めているものに継続的に集中することができます。
テストケースを効果的にレイアウトすることで、必要なすべてのアクションが確実に完了し、テストケースの可読性が向上し、実行の流れがスムーズになります。一貫性のある構造は、自己文書化可能なテストケースの構築に役立ちます。テストケースで一般的に用いられる構造は、(1)セットアップ、(2)実行、(3)検証、(4)クリーンアップです。
個人が従うべきベストプラクティスとしては、共通のセットアップおよびティアダウンロジックを適切なテストケースで使用されるテストサポートサービスに分離すること、各テストオラクルがテストの検証に必要な結果のみに焦点を当てるようにすること、および非リアルタイムオペレーティングシステムでの実行に対する許容範囲を設けるように時間関連のテストを設計することなどが挙げられます。実行遅延に対して 5~10% のマージンを設ける一般的なプラクティスは、テスト実行における偽陰性の可能性を減らします。また、テストコードは本番コードと同じように扱うことが推奨されます。テストコードは、ポジティブケースとネガティブケースの両方で正しく動作し、長期間にわたって動作し、読みやすく保守しやすいものでなければなりません。チームは集まってテストとテストプラクティスをレビューし、効果的なテクニックを共有し、悪い習慣を見つけることができます。[ 15 ]
テスト駆動開発は、受け入れテスト駆動開発(ATDD) と関連していますが、異なります。[ 16 ] TDD は主に、一連の操作を正しく実行する適切に記述されたコード単位 (関数、クラス、またはモジュール) を作成するのに役立つ開発者ツールです。ATDD は、要件が適切に定義されていることを保証するための顧客、開発者、およびテスター間のコミュニケーションツールです。TDD にはテスト自動化が必要です。ATDD には必要ありませんが、自動化は回帰テストに役立ちます。コード単位が要件の一部を実装するため、TDD で使用されるテストは ATDD テストから派生することがよくあります。ATDD テストは顧客が読みやすい必要があります。TDD テストは読みやすい必要はありません。
BDD(ビヘイビア駆動開発)は、TDDとATDDのプラクティスを組み合わせたものです。[ 17 ] テストを最初に書くというプラクティスを含みますが、実装単位をテストするテストではなく、動作を記述するテストに焦点を当てています。JBehave 、Cucumber、Mspec、Specflowなどのツールは、プロダクトオーナー、開発者、テストエンジニアが一緒に動作を定義し、それを自動テストに変換できる構文を提供します。
TDD(テスト駆動開発)において役立つテストフレームワークやツールは数多く存在する。
開発者は、コンピュータ支援テストフレームワーク(一般的にxUnitと総称される。これは1998年に作成されたSUnitから派生したものである)を使用して、テストケースを作成し、自動的に実行することができます。xUnitフレームワークは、アサーションスタイルのテスト検証機能と結果レポート機能を提供します。これらの機能は、実行検証の負担を独立した後処理アクティビティからテスト実行に含まれるアクティビティに移すため、自動化にとって非常に重要です。これらのテストフレームワークが提供する実行フレームワークにより、すべてのシステムテストケースまたはさまざまなサブセットを他の機能とともに自動的に実行できます。[ 18 ]
テストフレームワークは、1987年に作成された言語非依存のTest Anything Protocolで出力された単体テスト結果を受け入れることができる。
大規模システムでTDDを実行するには、モジュール型アーキテクチャ、公開インターフェースを備えた定義済みコンポーネント、プラットフォーム独立性を最大限に高めた規律あるシステムレイヤリングが必要です。これらの実績のあるプラクティスにより、テスト容易性が向上し、ビルドとテストの自動化の適用が容易になります。[ 11 ]
複雑なシステムには、さまざまな要件を満たすアーキテクチャが必要です。これらの要件の重要なサブセットには、システムの完全かつ効果的なテストをサポートすることが含まれます。効果的なモジュール設計により、効果的なTDDに不可欠な特性を共有するコンポーネントが生まれます。
効果的なモジュール型アーキテクチャを構築するための重要な手法はシナリオモデリングであり、これは一連のシーケンスチャートを作成し、それぞれが単一のシステムレベルの実行シナリオに焦点を当てます。シナリオモデルは、特定の刺激に対するコンポーネント間の相互作用の戦略を作成するための優れた手段となります。これらのシナリオモデルはそれぞれ、コンポーネントが提供しなければならないサービスまたは機能の豊富な要件セットとして機能し、これらのコンポーネントとサービスが相互作用する順序も規定します。シナリオモデリングは、複雑なシステムのTDDテストの構築を大幅に容易にすることができます。[ 11 ]
大規模なシステムでは、コンポーネントの品質が低いことによる影響は、相互作用の複雑さによって増幅されます。この増幅効果により、大規模プロジェクトではTDDのメリットがより早く現れます。しかし、テスト全体の複雑さ自体が問題となり、潜在的なメリットを損なう可能性があります。単純に聞こえるかもしれませんが、重要な最初のステップは、テストコードも重要なソフトウェアであり、本番コードと同じ厳密さで作成および保守されるべきであるという認識を持つことです。
複雑なシステム内でテストソフトウェアのアーキテクチャを作成および管理することは、コア製品のアーキテクチャと同様に重要です。テストドライバは、UUT、テストダブル、およびユニットテストフレームワークと相互作用します。[ 11 ]
テスト駆動開発(TDD)の実証研究では、結果はまちまちである。レビューでは一般的に、TDDがソフトウェア品質のいくつかの指標を改善できるという証拠が見つかるが、生産性への影響は一貫性に欠ける。27件の研究を対象とした2013年のメタ分析では、外部品質にはわずかなプラスの効果が見られ、生産性には全体的な効果はほとんど、あるいは全く見られず、産業界の研究では品質の向上は大きいものの、生産性の低下も大きいことがわかった。[ 19 ] 1999年から2014年の間に発表された研究を対象とした2016年の系統的レビューでは、ほとんどの研究で内部および外部のソフトウェア品質の改善が報告されているが、生産性の結果は学術環境と産業界環境で異なっていたことがわかった。[ 20 ]
実証研究の比較分析では、TDD は導入される欠陥を減らし、より保守しやすいコードにつながる可能性があり、実装されたコードの中にはより小さく、より複雑でないものがあるという結論が出ました。[ 21 ]しかし、プロの開発者を対象とした業界実験では、TDD の効果はタスクの特性に大きく依存することが判明し、TDD が産業環境におけるインクリメンタルなテスト後開発よりも優れているか劣っているかを判断する前に、さらなる証拠が必要であると結論付けました。[ 22 ] TDD プロセス特性に関する後の研究では、品質と生産性の向上は、テスト優先の順序付け自体よりも、小さく均一な開発ステップとより関連していることがわかりました。[ 23 ]
TDD は、開発者が自動化されたテストを繰り返し実行することで、開発中に迅速なフィードバックを提供できます。これにより、回帰バグが導入された直後に検出しやすくなり、動作変更に対するセーフティネットを提供することでリファクタリングをサポートできます。 [ 24 ]テストは対応する本番コードの前に記述されるため、開発者は実装前にコードの望ましい動作とインターフェースを考慮する必要があります。これにより、特に実装の詳細ではなく公開動作に対してテストが記述されている場合、より小さなコード単位、疎結合、およびより明確なインターフェースが促進されます。[ 24 ] [ 21 ]
TDDでは、開発の副産物として一連の自動テストが生成される傾向があります。このようなテストは、変更がマージまたはリリースされる前に頻繁にテストされる継続的インテグレーションのワークフローで役立ちます。この意味で、TDDは後々の変更に対する信頼性を高める可能性がありますが、単体テストに合格したからといって、ソフトウェアが正しいことが証明されるわけではありません。[ 19 ] [ 20 ]
TDDは、他のソフトウェアテスト手法の代替となるものではありません。TDDは一般的に単体テストを通じて実施されるため、ユーザーインターフェース、データベース、分散システム、ハードウェア、タイミング、セキュリティ特性、コンポーネント間の相互作用などに依存する動作を十分にテストできない場合があります。これらの領域では、多くの場合、追加の統合テスト、システムテスト、受け入れテスト、ユーザビリティテスト、またはその他の専門的なテスト手法が必要となります。
TDD(テスト駆動開発)で作成されたテストは、本番コードと同じ誤解を抱えている可能性があります。開発者が要件を誤って解釈した場合、テストと実装の両方に同じ誤った前提が組み込まれ、ソフトウェアに問題があるにもかかわらずテストが合格してしまうことがあります。そのため、テストが不完全であったり、範囲が狭すぎたり、外部から見える動作ではなく実装の詳細に焦点を当てていたりすると、多数のテストが合格したとしても、誤った安心感を生み出す可能性があります。
自動テストはメンテナンスコストも生み出します。内部実装の詳細に密接に結合しているテスト、過剰なモックを使用しているテスト、脆弱なタイミング仮定に依存しているテスト、または重複したセットアップコードを含むテストは、メンテナンスが困難になる可能性があります。[ 25 ]特に複雑なブラウンフィールドタスクや自動テストの経験が限られているプロジェクトなど、一部の状況では、テストの作成とメンテナンスに必要な時間が短期的な生産性を低下させる可能性があります。[ 22 ] [ 20 ]
第1回TDDカンファレンスは2021年7月に開催されました。[ 26 ]カンファレンスはYouTubeに録画されました。 [ 27 ]
{{cite web}}: CS1メンテナンス: アーカイブサービスは非推奨になりました (リンク)