私はコーディングに慣れていない。私は(真剣に)15年以上コーディングしています。私は常にコードのテストを行ってきました。ただし、過去数か月間、 Ruby on Rails を使用して テスト駆動の設計/開発 (TDD)を学習してきました。これまでのところ、私は利点を見ていません。
いくつかのテストを書くことにはいくつかの利点がありますが、ごくわずかです。最初にテストを書くというアイデアは好きですが、実際のコードをデバッグするよりも、テストをデバッグして実際の意味を説明するのにかなりの時間を費やしていることがわかりました。これはおそらく、テストコードがテストするコードよりもかなり複雑になることが多いためです。これが、利用可能なツール(この場合は RSpec )の経験不足であることを願っています。
けれども、現時点では、残念なことにパフォーマンスが不足していることに苛立ちを感じているレベルは、受け入れがたいほどです。これまでのところ、TDDから見た唯一の価値は、他のプロジェクト/ファイルのテンプレートとして機能するRSpecファイルのライブラリの拡大です。これは、実際のプロジェクトコードファイルほど有用ではありません。
利用可能な文献を読んでいると、TDDは前もって大規模な時間シンクであるように見えますが、最終的には成果を上げています。私はただ疑問に思っています、実際の例はありますか?この大規模な欲求不満が現実の世界で報われることはありますか?
この質問を他のどこかで見逃さなかったことを本当に望んでいます。検索しましたが、現時点ですべての質問/回答は数年前のものです。 TDDについて何か悪いことを言う開発者を見つけたのはまれなことでした。そのため、私はこれまで多くの時間を費やしてきました。しかし、実際の例を誰も指していないようです。私は、2011年にコードをデバッグしている人が完全な単体テストスイートを用意してくれてありがとうと言った1つの回答を読みました(2008年にコメントがあったと思います)。
だから、私はただ疑問に思っています、これらすべての年の後に、私たちはついにペイオフが現実であることを示す例がありますか? TDDを使用して設計/開発され、ユニットテストの完全なセットがあり、実際に見返りを感じたコードを実際に継承または戻した人はいますか?それとも、テストが何をテストしているかを理解しようとするのに多くの時間を費やしていて(そしてなぜそれが重要だったのか)、ごちゃごちゃ全体を捨ててコードを掘り下げただけだと思いましたか?
このホワイトペーパー は、TDDが開発時間を15〜35%追加する代わりに、他の同様のプロジェクトの欠陥密度を40〜90%削減することを示しています。
記事は全文を参照しています (pdf) -Nachiappan Nagappan、E。Michael Maximilien、Thirumalesh Bhat、およびLaurie Williams。 「テスト駆動開発による品質改善の実現:4つの産業チームの結果と経験」 ESE 2008。
Abstractテスト駆動開発(TDD)は、数十年に渡って散発的に使用されてきたソフトウェア開発の実践です。この実践により、ソフトウェアエンジニアは毎分失敗する単体テストを作成してから、それらのテストに合格するための実装コードを作成するまでの時間。テスト駆動開発は、アジャイルソフトウェア開発方法論の重要な有効化プラクティスとして最近再浮上しています。ただし、ほとんどの実証的証拠は、産業のコンテキストでのこのプラクティスの有用性をサポートまたは反駁しています。ケーススタディは、TDDを採用したMicrosoftの3つの開発チームとIBMの1つの開発チームで実施されました。ケーススタディの結果は、4つの製品のリリース前の欠陥密度が、TDD手法を使用しなかった同様のプロジェクトと比較して40%から90%減少したことを示しています。主観的に、チームはTDDを採用した後の初期開発時間を15〜35%増加させました。
フルペーパーはまた、TDDに関する関連する実証的研究とそれらの高レベルの結果(セクション3関連研究)を簡潔にまとめています。これには、George and Williams 2003、Muller and Hagner(2002)、Erdogmus et al al。 (2005)、ミュラーとティシー(2001)、ジャンゼンとセイディアン(2006)。
いくつかのテストを書くことにはいくつかの利点がありますが、ごくわずかです。最初にテストを書くというアイデアは好きですが、実際のコードをデバッグするよりも、テストをデバッグして実際の意味を説明するのにかなりの時間を費やしていることがわかりました。
私は過去3年間TDDで働いており、私の経験は正反対です。単体テストを作成しなかった場合は、コードのデバッグよりも単体テストの作成に費やす時間が短縮されます。
TDDを行うだけでなく、アウトサイドインで作業します。つまり、最初にトップ/ GUIレイヤーのTDDを実装します。最上層を実装すると、システムの次の層の要件が定義されます。これは、機能に必要なすべてのコードが実装されるまで、TDDなどを使用して開発します。このような機能を実装した後、実際のシステムで機能を喫煙テストした後、初めて機能することがよくあります。いつもではありませんが、よくあります。
また、実際のシステムの機能をスモークテストするのに、2、3のユニットテストを実行するよりもはるかに長い時間がかかることを考えると、膨大な時間を節約できます。単体テストをまったく作成しない機能を実装しないよりも、TDDを使用して機能を実装する方が実際には高速です。
ただし、単体テストの記述は、他のプログラミングスキルと同様に、習得して習得する必要があるスキルです。 TDDを始めたとき、プログラミングの専門家として12年の経験があり、非常に熟練したプログラマーでした。システムコード用の大規模なテストスイートを作成するのは簡単だと思いました。しかし、テストコードの量が増え、システムのさまざまな部分が変更され、既存のテストを変更する必要が生じたため、単体テストの構造化と記述自体が、習得して習得する必要のあるスキルであることを学びました。また、すべてのコードが同等にテストできるわけではありません。効率的にテストできるようにするには、システムコードを非常に疎結合にする必要があります。実際にTDDを学ぶことは、システムコードをより疎結合にするのに役立ちました。
しかし、TDDの作業における私の現在の効率は、ユニットテストの記述方法を習得するだけでなく、システムが実装されているテクノロジ(この場合はC#)を習得することの両方の組み合わせに由来しています。
新しいテクノロジーを学びながらTDDを行うのは難しい場合があります。私は少しiPhoneプログラミングを行っていますが、言語、Objective Cを習得しておらず、ライブラリも習得していないため、大量の単体テストを作成していません。だから私はユニットテストをどのように構成するのか、システムコードをどのように構成してテスト可能にするのかさえもわかりません。
しかし、それは実際のプロジェクトでどれだけうまく機能するのでしょうか?
私がこの2年間取り組んでいるプロジェクトでは、ユニットテストでコードを十分にカバーする必要があるという要件がありますが、最初にテストを作成するのは私だけです。しかし、大規模なテストスイートは、システムをリファクタリングできることに自信を与え、テストスイートに合格した場合にシステムが正しく動作することを確信しています。
しかし、残念なことに、多くのテストはシステムコードの後に記述されているため、かなりの数のテスト自体に問題があります。つまり、テスト対象のものを実際にはテストしていません。これは避けられません。コードを記述するたびに、コードが意図したとおりに機能しない可能性があります。つまり、バグが存在します。テストコードについても同様です。したがって、テストすべきコードが意図したとおりに機能していなくても、合格するテストを作成する可能性があります。
最初にテストを記述し、テストエラーが発生することだけでなく、システムコードを実装する前に期待どおりのエラーメッセージでテストが失敗することを確認することで、ユニットテストコードのバグのリスクを大幅に軽減します。
要約すると、私の経験では、TDDの技術を習得したら、長期的には時間を節約できるだけでなく、時間を前もって節約できるようになります。しかし、経験豊富なプログラマであっても、TDDの技術を習得するには時間がかかります。また、さまざまなスキルを持つ開発者のチームがTDDの技術を習得するには、さらに時間がかかります。
私たちは大きな利益を得ました。
私たちはTier 1 Merchantです。つまり、年間600万以上の支払い取引を処理しています。
私たちの支払いゲートウェイシステムには、何千ものユニットテストと統合テストがあります。これらのテストは、支払いを処理する能力に自信を与えます。あなたは自分の車のブレーキが機能することを確信したいと思いませんか?支払いを処理できないため、ビジネスを失うことはないと確信しています。
コードカバレッジはあなたにその自信を与えます。もちろん、それだけでは十分ではありませんが、非常に良いスタートです。
私たちの支払いゲートウェイシステムのほとんどはTDDを使用して作成されました。いくつかの側面はテストするのがかなり難しいので、コードカバレッジを犠牲にすることによって、手抜きをすることにしました。私たちは戻ってこれらの問題に最終的に対処します。
個人的には、テストを書く前にロジックを書くのは難しいと思います。そうは言っても、TDDの考え方を始めるには少し時間がかかりました。
Visa PCIリファレンス: http://usa.visa.com/merchants/risk_management/cisp_merchants.html
多くの場合、テストはあまりにも多くのことを行う方法と考えられているかもしれませんが、場合によっては、問題を起こすだけの価値があると思います。
私は約3か月間、学校向けのKiller Sudokuソルバーを開発してきました。それは、可能性と解決策を取り除くために多くの「戦略」を使用しています。実際には、可能性のエラーは致命的であり、数独を解決する際に問題が発生する可能性があります。なぜなら、いくつかの可能性が取り除かれると、それをもう試すことはなく、それが解決策であった場合、プログラムは解決できません。もうグリッド。
しかし、手動でテストするのは本当に難しいです。つまり、グリッドがあるので、実際の例ではどの戦略が何を行っているかを確認できますが、戦略が適用されるたびにすべてをチェックすることはできません。これはデータが多すぎるためです。
そして、特定のグリッドに適用された戦略はかなり「ランダム」です。つまり、特定のグリッドですべてを使用するわけではありません。
だから私は各戦略のテストを書き、すべてのセルの結果をチェックし、単純な状況(たとえば、2つのセルだけですでに可能性が取り除かれている)を使用して、残念ながら解決できないグリッドがあったときに1日何時間も節約できました。問題がどこにあるのか私はすでに知っていたからです。
TDDの利点は、実際のコードを記述する前に、コードをcallにする方法を理解できることです。
つまり、TDDはAPIの設計に役立ちます。
私の経験では、これによりAPIが向上し、コードが向上します。
編集:私が書いたように、これは「私の経験」、つまり「現実の世界のプロジェクト」を書くときでしたが、残念ながらこれは私が世界に見せることができない閉じたソースコードベースでの問題です。これは実際にが求められていることであり、単なるそのようなプロジェクトの存在の確認ではないことをコメントから理解できます。
また、私の個人的な経験では、要件が変化する傾向があるため、メンテナンスモードに入ると、真のメリットが示されることもわかりました。よりクリーンなAPIにより、新しいまたは変更された要件をテストコードで表現するのがはるかに簡単になり、すべてのテストでveryコードがどのように呼び出され、何ができるかを将来のメンテナーに見やすくしました期待される。
テストケースは仕様のバージョンを実行しており、APIを呼び出す方法を非常に簡単に確認できます。これはおそらく、これまでに見た"HOW"-documentationの最も便利な形式です(JavaDocのような"WHY"-documentationと比較してください)。それは正しいです(さもなければ、テストは失敗します)。
最近、私はスクリプト可能なftpクライアントを、アプリケーションの動作に影響を与える非常に大きなオプションセットで維持する必要がありました。 TDDは最近、新機能のために導入されており、大規模なテストスイートにより、使用された機能が期待どおりに機能することを確信しながら、修正プログラムを実行できます。言い換えれば、この移行は非常に迅速に成果を上げることが証明されました。
テストへの特定のアプローチがどれだけ価値があるかは、開発中のシステムがどれほどミッションクリティカルであるか、および他のいくつかのミッションクリティカルシステムがどれだけシステムに依存しているかによって異なります。ウェブサイトのシンプルなゲストブックスクリプトはミッションクリティカルとは言えませんが、それが実行されているウェブサイトが、データベースへのフィルタリングされていない入力を許可するバグによって危険にさらされ、そのサイトが重要なサービスを提供している場合、突然、はるかに多くなります。ゲストブックのスクリプトを徹底的にテストするために重要です。フレームワーク/ライブラリコードについても同様です。バグのあるフレームワークを開発する場合、そのフレームワーク機能を使用するすべてのアプリケーションにも同じバグがあります。
テスト主導の開発により、テストに関して安全性がさらに高まります。テストするコードと一緒に、またはコードの後にテストを作成すると、テストが誤って実行されるという実際のリスクがあります。最初にすべてのテストを作成する場合、コードが内部でどのように機能するかは、テストの作成対象に影響を与えないため、特定の誤った出力が正しいと考えるテストを誤って作成する可能性が低くなります。
テスト駆動開発では、開発者がさらに多くの作業を行いたくないので、テストしやすいコードを作成することも推奨されます。テストが容易なコードは、理解、再利用、保守が容易なコードである傾向があります。
また、メンテナンスはTDDのメリットを実際に享受する場所です。ソフトウェアに費やされるプログラミング作業の大部分は、メンテナンスに関連しています。つまり、ライブコードに変更を加えて、新しい機能を追加したり、バグを修正したり、新しい状況に適応させたりします。このような変更を行う場合は、行う変更が期待どおりの効果を持つことを確認する必要があります。さらに重要なのは、予期しない影響が発生しないことです。コードの完全なテストスイートがある場合、行った変更が他のものを壊していないことを簡単に確認できます。加えた変更が他のものを壊してしまった場合は、その理由をすばやく見つけることができます。メリットは長期的です。
あなたはあなたの質問で次のように述べました
いくつかのテストを書くことにはいくつかの利点がありますが、ごくわずかです。最初にテストを書くというアイデアは好きですが、実際のコードをデバッグするよりも、テストをデバッグして実際の意味を説明するのにかなりの時間を費やしていることがわかりました。これはおそらく、テストコードがテストするコードよりもかなり複雑になることが多いためです。これが利用可能なツール(この場合はrspec)の経験不足であることを願っています。
これは、テストが十分に行われていないことを示唆しているようです。単体テストは、非常に単純で、一連のメソッド呼び出しに続いて、期待される結果を実際の結果と比較するアサーションが続くものと想定されています。テストのバグは悲惨なものになるため、これらは単純であることを意図しています。ループ、分岐、または他のプログラムを導入すると、テストに制御がスローされ、テストにバグが導入される可能性が高くなります。テストのデバッグに多くの時間を費やしている場合は、テストが非常に複雑であり、テストを簡略化する必要があることを示しています。
テストを単純化できない場合、その事実だけでテスト中のコードに問題があることを示唆しています。たとえば、クラスに長いメソッド、多くのif/elseif/elseまたはswitchステートメントを含むメソッド、またはクラスの現在の状態によって決定される複雑な相互作用を持つ多数のメソッドがある場合、テストは必然的に非常に複雑になります完全なコードカバレッジを提供し、すべての不測の事態をテストします。クラスに他のクラスへの依存関係がハードコーディングされている場合、これにより、コードを効果的にテストするためにジャンプする必要があるフープの数が再び増加します。
クラスを小さく、高度に集中し、実行パスの少ない短いメソッドで内部状態を排除しようとすると、テストを簡略化できます。そして、これは問題の核心のようなものです。優れたコードは本質的にテストが簡単です。コードのテストが簡単でない場合は、おそらくコードに問題があります。
単体テストを書くことは、長期的にはあなたに利益をもたらすものであり、それらを回避することは単に後でトラブルを蓄積することです。あなたは技術的負債の概念に精通していないかもしれませんが、それは金融負債とよく似ています。テストを書いていない、コードをコメントしていない、ハードコードされた依存関係を書いているなどは、借金をする方法です。あなたは早い段階で手抜きをすることで時間を「借りる」ので、これは厳しい締め切りに間に合うのに役立つかもしれませんが、プロジェクトの早い段階で節約した時間はローンです。コードをクリーンアップせずに経過したり、適切にコメントしたり、テストスイートを構築したりすることは、毎日あなたに興味を抱かせます。それが長く続くほど、より多くの関心が蓄積されます。やがて、コードがもつれた混乱状態になり、意図しない結果を引き起こさずに変更を加えることができなくなることがわかります。プロジェクトの早い段階で取り戻し、後で返済できなかったタイムローンへの関心が戻ってきて、あなたは事実上破産しました-既存のシステムの問題を修正するのにかかる時間は、かかる時間を超えていますゼロから書き直す。
ユニットテストを早期に作成し、それらを最新の状態に保つことを「テクニカルクレジット」の一種と考えることができます。あなたはプロジェクトの早い段階でそれを適切な慣行に費やすことにより、銀行に時間を費やしています。プロジェクトのメンテナンスフェーズに入ると、この先見性に関心が集まります。変更を加えたい場合は、変更の正確性を確認し、不要な副作用がないことを簡単に検証できます。また、大騒ぎすることなく、すぐに更新を入手できます。バグが発生した場合は、バグを実行する新しい単体テストを追加し、コードでバグを修正できます。次に単体テストを実行すると、バグが修正されたこと、および他の問題が発生していないことを確認できます。さらに、以前に修正されたバグが再導入される「リグレッション」を回避できます。これは、物事が変更された理由を文書化していないためです。
TL:DR-はい、それらは実際の助けですが、投資です。メリットは後で明らかになるだけです。
私は仕事でTDDを頻繁に使用しています。私の経験では、追加の時間や労力を払う必要がないのでTDDはそれ自体を正当化し、あなたはそれを保存します。
TDDを使用しているので、デバッグなどに費やす時間ははるかに少なくなります。テストに合格しない限り、生産性の高いコードは書かれたとは見なさないため、最初から機能します。
QAが報告するバグははるかに少ないため、リリース後にコードを修復するコストを節約できます。これは、TDDではテストなしでコードを記述できないため、コードカバレッジが大幅に向上するためです。
アプリケーションサーバー全体を起動する必要がないため、(生産的な)コードをより頻繁かつ迅速に実行できます。単体テストを開始すると、桁違いに速くなります。もちろん、私が生産的なコードを試したいときにテストがすでに実行可能である場合にのみ、それから利益を得ます。テストが後で来るとき、この利点は見逃されます。
手作業によるテストははるかに少なくなります。 TDDを実践していない私の同僚は、新しいコードが実行されるポイントに到達するまで、アプリケーションをクリックするのに多くの時間を費やしています。バージョン管理にコミットする直前に、手動で1回だけテストします。
デバッガを使用しても、アプリケーション全体よりもテストの実行をデバッグする方がはるかに高速です。
たぶんあなたはユニットテストを回帰テストと考えるかもしれません。それはそれらの目的の1つですが、開発のためのツールを開発ツールとして理解することは、それらをより価値のあるものにします。
顧客受け入れテスターまたは(天国では禁止)プロダクションユーザーがバグを発見すると、別の利点(回答した他の人が述べた利点に加えて)が始まります。バグレポートを、問題があると思われるクラスのTDDスタイルのテストに変えます。それが失敗するのを見てください。修理する。それが通過するのを見てください。その後、バグを修正したことがわかります。この手法により、時間を節約できました。
まあ、私は個人的には仲間の開発者の2倍の速さで利益を上げ、開発者がTDDを行わないため、彼らがするバグの半分未満しか書かないことを知っています。おそらく私よりも優れているはずの人...私は彼らを少なくとも2倍上回っています。
私はすぐにそこに着きませんでした。私は、袖口を外して、ハーネスなしでコードを書くのがかなり得意です。この余分ながらくたをすべて書くのは大きな無駄のようでした。しかし、それは(排他的ではない)を含むいくつかのことをします:
この後のビットの例は、私が現在取り組んでいるプロジェクトであり、リードが突然、ほとんど使用されていない理由で、使用していた通信プロトコルを完全に書き直すことにしました。私は2時間でその変化に対応することができました。それを他のすべてのものから切り離し、最後にそれを結び付けて統合テストするまで、完全に独立してそれに取り組むことができたからです。私の同僚のほとんどは、コードが分離されず、どこでも、あらゆる場所で変更されているので、おそらく1日以上かかっていたでしょう...すべての場所でコンパイルしている...すべてをコンパイルしている...統合テスト...繰り返し、繰り返し...その方法の方がずっと長くかかり、安定しているとは言えません。
答えはイエスです。私の会社では、20年以上にわたってC++アプリケーションを開発しています。昨年、私たちはいくつかの新しいモジュールでTDDに入り、欠陥率は大幅に減少しました。私たちは非常に気に入ったため、変更を加えるたびに、レガシーコードにテストを追加することさえあります。
さらに、モジュール全体が、バグを示すことなく、最初から最後まで、製造段階で完成しました(これも重要なモジュールです)。そのため、通常、そうでない場合に発生するのはモジュールが「完了」し、バグ修正のためのベータテストから4〜5回しか返らないため、その開発は通常よりも速くなりました。これは大幅な改善であり、開発者は新しいプロセスにも満足しています。
私は多くのRails TDDを実行していませんが、C++、C#、JavaおよびPythonで多くのことを実行しました。間違いなく動作します。十分な自信がないため、テスト名について考えるのに多くの時間を費やしていると思います。最初にテストを記述してください。
TDDのコツをつかむと、「このテストにどのように名前を付けますか?」ということに少し気を使い始めて、すでに記述されているリファクタリングと適応を行って、それに気づきました。現在の状況に合わせてテストします。
だから、私があなたに最も役立つはずだと思うヒントは、あまり心配しないことです。 TDDの最も良い点の1つは、既に記述され機能しているものを変更することについて勇気を与えることです。そして、それはテストを含みます。
単純な「canCreate」テストで新しいクラステストを開始します。「OK、このクラスに取り組んでいます...正しい」のように、正しい方向に向けるだけです。
次に、追加のテストを開始しますが、一度に1つだけです。作成する各テストが、その時点で頭に浮かぶ次の最も単純なケースであることを確認してください(30秒以内で考えてから、タイムアウトします)あなたがその時点で持っている最高のもので)。
既存のテストをリファクタリングしたり、古いテストや冗長なテストを削除したりする心配はありません。多くの人はこれを理解していませんが、TDDでは実際に1つの価格で2つのセーフティネットを取得しています。テストは製品コード変更のセーフティネットですが、製品コードはテストをリファクタリングするためのセーフティネットでもあります。関係は相互です。これは、実際には密結合の良い例です。
別のショットを与えます。そして、私は Clean Code Casts 、特にTDDに関するものを視聴することをお勧めします。
実際の重要な例:
データ構造変換関数を作成する必要がありました。入力はデータ構造(実際にはツリーのようにネストされたデータ構造)であり、出力は同様のデータ構造になります。実際の変容を思い描くことはできませんでした。 TDDの主な利点の1つ(とにかく私にとって)は、続行する方法がわからない場合にベビーステップを強制することです(Kent Becksの「例によるTDD」を参照)。これがどこに行くのかわからなかったので、空の入力または単純な入力のような単純な基本ケースから始めて、すべてがカバーされると思うまで、より複雑なケースまで進みました。結局、動作するアルゴリズムとそれを証明するテストができました。テストは私の実装が現在機能していることを証明するだけでなく、後で何かを台無しにすることも防ぎます。実装をテスト駆動しているため、分離されており、APIはクリーンです。