web-dev-qa-db-ja.com

単体テストの代わりに受け入れテストと統合テストを使用するだけで十分ですか?

この質問の簡単な紹介。現在TDDを使用しており、最近はBDDを1年以上使用しています。私はモックのようなテクニックを使用して、テストをより効率的に記述しています。最近、自分のために少しのお金の管理プログラムを書くための個人的なプロジェクトを始めました。私はレガシーコードを持っていなかったので、TDDから始めるのに最適なプロジェクトでした。残念ながらTDDの楽しさはそれほど感じませんでした。それは私の楽しみを台無しにして、プロジェクトをあきらめました。

なにが問題だったの?まあ、私はTDDのようなアプローチを使用して、テスト/要件にプログラムの設計を進化させました。問題は、テストの作成/リファクタリングに関して、開発時間の半分以上でした。したがって、最終的にはリファクタリングして多くのテストに書き込む必要があるため、これ以上の機能を実装したくありませんでした。

仕事で私は多くのレガシーコードを持っています。ここでは、統合テストと受け入れテストを増やし、単体テストを減らしています。バグは主に受け入れテストと統合テストによって検出されるため、これは悪いアプローチではないようです。

私の考えは、結局、単体テストよりも多くの統合テストと受け入れテストを書くことができるということでした。バグを検出するために言ったように、単体テストは統合/受け入れテストよりも優れていません。単体テストも設計に適しています。以前はそれらをたくさん書いていたので、私のクラスは常にテストしやすいように設計されています。さらに、テスト/要件に設計を導くアプローチは、ほとんどの場合、より良い設計につながります。単体テストの最後の利点は、それらが高速であることです。私は知るために十分な統合テストを書きました、それらはユニットテストとほとんど同じくらい速くなることができるということです。

私がウェブを調べていた後、私が言及した私のものと非常によく似たアイデアが here および there であることがわかりました。この考えについてどう思いますか?

編集

デザインが良かった1つの例の質問に答えましたが、次の要件のために巨大なリファクタリングが必要でした。

最初は、特定のコマンドを実行するためのいくつかの要件がありました。私は拡張可能なコマンドパーサーを作成しました-ある種のコマンドプロンプトからのコマンドを解析し、モデルで正しいものを呼び出しました。結果はビューモデルクラスで表現されました。 First design

ここには何も問題はありませんでした。すべてのクラスは互いに独立していて、新しいコマンドを簡単に追加したり、新しいデータを表示したりできました。

次の要件は、すべてのコマンドに独自のビュー表現(コマンドの結果のある種のプレビュー)が必要であることです。私はプログラムを再設計して、新しい要件に対してより良い設計を実現しました。 Second design

すべてのコマンドに独自のビューモデルがあり、したがって独自のプレビューがあるため、これも良かったです。

問題は、コマンドパーサーがコマンドのトークンベースの解析を使用するように変更され、コマンドを実行する機能が削除されたことです。すべてのコマンドは独自のビューモデルを取得し、データビューモデルは現在のコマンドビューモデルのみを認識します。現在のコマンドビューモデルは、表示する必要があるデータを認識しています。

この時点で私が知りたかったのは、新しいデザインが既存の要件を満たさなかったかどうかです。受け入れテストを変更する必要はありませんでした。ほぼすべての単体テストをリファクタリングまたは削除する必要があり、膨大な作業量でした。

ここで紹介したいのは、開発中に頻繁に発生した一般的な状況です。古いデザインでも新しいデザインでも問題はありませんでした。要件によって自然に変化しました。私がそれを理解した方法は、これがTDDの1つの利点であり、デザインが進化することです。

結論

すべての回答と議論をありがとう。この議論の要約として、私は次のプロジェクトでテストするアプローチを考えました。

  • まず最初に、いつものように実装する前にすべてのテストを記述します。
  • 要件については、プログラム全体をテストするいくつかの受け入れテストを最初に記述します。次に、要件を実装する必要があるコンポーネントの統合テストをいくつか記述します。この要件を実装するために別のコンポーネントと緊密に連携するコンポーネントがある場合、両方のコンポーネントを一緒にテストするいくつかの統合テストも記述します。最後に重要なのは、アルゴリズムやその他の高い順列を持つクラスを作成する必要がある場合です。シリアライザ-この特定のクラスの単体テストを記述します。他のすべてのクラスはテストされませんが、単体テストはテストされます。
  • バグの場合、プロセスを簡略化できます。通常、バグは1つまたは2つのコンポーネントによって引き起こされます。この場合、バグをテストするコンポーネントの統合テストを1つ記述します。アルゴリズムに関連する場合は、単体テストのみを記述します。バグが発生しているコンポーネントを簡単に検出できない場合は、バグを特定するための受け入れテストを作成します。これは例外です。
63
Yggdrasil

オレンジとリンゴを比較しています。

統合テスト、受け入れテスト、単体テスト、動作テスト-これらはすべてテストであり、コードの改善に役立ちますが、まったく異なります。

私は私の意見でさまざまなテストのそれぞれを検討し、うまくいけばそれらすべてのブレンドが必要な理由を説明します:

統合テスト:

簡単に言えば、システムのさまざまなコンポーネント部分が正しく統合されていることをテストします。たとえば、Webサービス要求をシミュレートして、結果が返されることを確認します。私は一般に、実際の(イッシュな)静的データとモックされた依存関係を使用して、一貫して検証できることを確認します。

受け入れテスト:

受け入れテストは、ビジネスユースケースに直接関連付ける必要があります。非常に大きい(「取引が正しく送信される」)か、小さい(「フィルターがリストを正常にフィルターする」)かは関係ありません。重要なのは、特定のユーザー要件に明示的に関連付けられる必要があるということです。テスト駆動開発ではこれらに焦点を当てたいと思います。というのも、開発者とqaがユーザーストーリーを検証するためのテストの優れたリファレンスマニュアルがあるからです。

単体テスト:

個々のユーザーストーリーを単独で構成する場合と構成しない場合がある機能の小さな個別のユニットの場合-たとえば、特定のWebページにアクセスしたときにすべての顧客を取得すると言うユーザーストーリーは、受け入れテスト(Webへのアクセスをシミュレート)ページと応答の確認)がいくつかの単体テストを含む場合もあります(セキュリティ権限が確認されていることを確認し、データベース接続が正しくクエリを実行していることを確認し、結果の数を制限するコードが正しく実行されていることを確認します)-これらはすべて「単体テスト」ですこれは完全な受け入れテストではありません。

行動テスト:

特定の入力の場合に、アプリケーションのフローを定義します。たとえば、「接続を確立できない場合は、システムが接続を再試行することを確認してください。」繰り返しになりますが、これは完全な受け入れテストになる可能性は低いですが、それでもまだ何か有用なものを検証することができます。

これらはすべて、テスト作成の多くの経験を通じて私の意見です。教科書のアプローチに集中するのは好きではありません。むしろ、テストに価値を与えるものに集中してください。

37
Michael

TL; DR:それがあなたのニーズを満たす限り、はい。

私はこれまで何年もの間、アクセプタンステスト駆動開発(ATDD)開発を行ってきました。それは非常に成功することができます。注意すべきことがいくつかあります。

  • ユニットテストは、IOCの実施に役立ちます。単体テストなしでは、責任は開発者が適切に記述されたコードの要件を満たしていることを確認することです(単体テストが適切に記述されたコードを駆動する限り)。
  • 通常はモックされているリソースを実際に使用している場合は、速度が遅くなり、誤ったエラーが発生する可能性があります。
  • テストでは、単体テストのように特定の問題を特定しません。テストの失敗を修正するには、さらに調査を行う必要があります。

今のメリット

  • はるかに優れたテストカバレッジ、統合ポイントをカバーします。
  • システム全体がソフトウェア開発の全体的なポイントである許容基準を確実に満たすようにします。
  • 大規模なリファクタリングをはるかに簡単、高速、そして安価にします。

いつものように、分析を行い、このプラクティスがあなたの状況に適しているかどうかを判断するのはあなた次第です。多くの人とは異なり、理想的な正しい答えはないと思います。それはあなたのニーズと要件に依存します。

40
dietbuddha

まあ、私はTDDのようなアプローチを使用して、テスト/要件にプログラムの設計を進化させました。問題は、テストの作成/リファクタリングに関して、開発時間の半分以上が

単体テストは、使用されるコンポーネントのパブリックインターフェイスがあまり頻繁に変更されない場合に最適に機能します。これは、コンポーネントがすでにうまく設計されている場合を意味します(たとえば、SOLIDの原則に従って)。

したがって、優れた設計がコンポーネントで多くの単体テストを「投げる」ことから「進化する」と信じるのは誤りです。 TDDは優れたデザインの「教師」ではありません。デザインの特定の側面(特にテスト容易性)が優れていることを確認するのに少しだけ役立ちます。

要件が変化し、コンポーネントの内部を変更する必要がある場合、これはユニットテストの90%を壊すため、非常に頻繁にそれらをリファクタリングする必要があり、設計はおそらくそれほど良くありませんでした。

ですから私のアドバイスは、作成したコンポーネントの設計について考え、オープン/クローズの原則に従ってコンポーネントをどのようにして作ることができるかを考えてください。後者の考え方は、コンポーネントの機能を変更せずに後で拡張できることを確認することです(したがって、単体テストで使用されるコンポーネントのAPIを壊さないようにします)。このようなコンポーネントは単体テストテストでカバーできます(カバーする必要があります)。エクスペリエンスは、説明したほど苦痛を伴うものではありません。

このような設計をすぐに思い付かない場合は、受け入れテストと統合テストを実際に開始するとよいでしょう。

編集:場合によってはコンポーネントの設計がうまくいくこともありますが、ユニットテストの設計によって問題が発生する可能性があります。簡単な例:クラスXのメソッド「MyMethod」をテストして、

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(値に何らかの意味があると仮定します)。

さらに、量産コードではX.MyMethodへの呼び出しは1つだけであると想定します。ここで、新しい要件として、メソッド「MyMethod」には追加のパラメーター(たとえば、contextなど)が必要ですが、これは省略できません。単体テストがなければ、呼び出しコードを1か所でリファクタリングする必要があります。単体テストでは、500か所をリファクタリングする必要があります。

しかし、ここでの原因は単体テスト自体ではなく、「X.MyMethod」への同じ呼び出しが厳密に「Do n't Repeat Yourself(DRY)」の原則に従っていないという事実だけです。したがって、ソリューションここでは、テストデータと関連する期待値をリストに入れ、ループで「MyMethod」の呼び出しを実行します(または、テストツールがいわゆる「データドライブテスト」をサポートしている場合は、その機能を使用します)。これにより、メソッドシグネチャが1(500ではなく)に変更されたときにユニットテストで変更する場所の数。

実際のケースでは状況はより複雑になる可能性がありますが、理解していただければ幸いです。単体テストでコンポーネントAPIが使用されており、変更される可能性があるかどうかわからない場合は、数を減らしてください。そのAPIへの呼び出しの数を最小限に抑えます。

18
Doc Brown

はい、もちろんです。

このことを考慮:

  • 単体テストは、小さなコードを実行する、小さな対象を絞ったテストです。きちんとしたコードカバレッジを実現するためにそれらの多くを記述して、すべて(または扱いにくいビットの大部分)をテストします。
  • 統合テストは、コードの広い範囲を実行する、大規模で広範なテストです。適切なコードカバレッジを実現するためにそれらのいくつかを記述して、すべて(または扱いにくいビットの大部分)がテストされるようにします。

全体的な違いを見てください...

問題はコードカバレッジの1つです。統合/受け入れテストを使用してすべてのコードの完全なテストを達成できれば、問題はありません。コードがテストされます。それが目標です。

すべてのユニットが実際にうまく機能することを確認するためだけにすべてのTDDベースのプロジェクトでいくつかの統合テストが必要になるため、それらを混同する必要があると思います(100%合格したユニットテスト済みコードベースは必ずしも機能しないことが経験からわかっています)それらをすべて組み合わせると!)

問題は実際には、テスト、障害のデバッグ、および修正の容易さに起因します。一部の人々は、単体テストがこれで非常に優れていると感じます。それらは小さく単純であり、失敗が見やすいですが、欠点は、ユニットテストツールに合わせてコードを再編成し、それらの多くを作成する必要があることです。統合テストは、多くのコードをカバーするために書くのがより困難であり、失敗のデバッグにはロギングなどのテクニックを使用する必要があるでしょう(とにかくこれを行う必要があると思いますが、失敗を単体テストすることはできません)現場にいるとき!).

どちらにしても、テスト済みのコードを取得できます。どちらのメカニズムがより適しているかを判断する必要があります。 (私は少し混ぜて、複雑なアルゴリズムを単体テストし、残りを統合テストします)。

9
gbjbaanb

TDDの「メリット」は、テストが作成されると自動化できることです。反対に、開発時間のかなりの部分を消費する可能性があります。これが実際にプロセス全体を遅くするかどうかは問題ではありません。事前のテストにより、開発サイクルの終わりに修正されるエラーの数が減るという議論があります。

これは、BDDの出番です。動作は単体テストに含めることができるため、プロセスは定義上、抽象性が低く、具体的です。

明らかに、無限の時間が利用可能であれば、さまざまな種類のテストをできる限り多く行うことになります。ただし、時間は一般に限られており、継続的なテストはある程度の費用対効果があります。

これはすべて、最も価値のあるテストがプロセスの最初にあるべきであるという結論につながります。これ自体は、あるタイプのテストを別のタイプのテストよりも自動的に優先するわけではありません。それよりも、それぞれのケースでそのメリットを考慮する必要があります。

個人用のコマンドラインウィジェットを作成する場合は、主に単体テストに関心があります。一方、Webサービスでは、かなりの量の統合/動作テストが必要になります。

ほとんどの種類のテストは「レーシングライン」と呼ばれるものに集中していますが、今日のビジネスで必要なもののテストですが、ユニットテストは後の開発フェーズで表面化する可能性のある微妙なバグを取り除くのに優れています。これは容易に測定できない利点であるため、見過ごされがちです。

2
Robbie Dee

それは恐ろしい考えだと思います。

受け入れテストと統合テストはコードのより広い部分に触れて特定のターゲットをテストするので、それらはmoreリファクタリングを必要とするでしょう。さらに悪いことに、コードの幅広いセクションをカバーしているため、より広い範囲から検索できるため、根本原因の追跡に費やす時間が増えます。

いいえ、UIが90%の奇妙なアプリや、ユニットテストに不便なものがない限り、通常はより多くのユニットテストを記述する必要があります。直面している苦痛は、単体テストからではなく、テストの最初の開発からです。一般に、ほとんどのテストの作成には、時間の3分の1を費やすべきです。結局のところ、彼らはあなたに仕えるためにあり、その逆ではありません。

2
Telastyn

単体テストの最後の利点は、それらが高速であることです。私は知るために十分な統合テストを書きました、それらはユニットテストとほとんど同じくらい速くなることができるということです。

これがポイントであり、「最後のアドバンテージ」だけではありません。プロジェクトがますます大きくなると、統合受け入れテストはますます遅くなります。そして、ここで私はあなたがそれらを実行するのをやめるようにとても遅いことを意味します。

もちろん、単体テストも遅くなっていますが、それでも桁違いに速くなっています。たとえば、私の前のプロジェクト(c ++、約600 kLOC、4000の単体テスト、および200の統合テスト)では、すべてを実行するのに約1分、統合テストを実行するのに15以上かかりました。変更されるパーツの単体テストを作成して実行するには、平均で30秒未満かかります。あなたがそれをそんなに速くすることができるとき、あなたはいつもそれをしたいと思うでしょう。

明確にするために:統合テストと受け入れテストを追加しないとは言いませんが、TDD/BDDを間違った方法で行ったようです。

単体テストも設計に適しています。

はい、テスト容易性を念頭に置いて設計すると、設計が改善されます。

問題は、テストの作成/リファクタリングに関して、開発時間の半分以上でした。したがって、最終的にはリファクタリングして多くのテストに書き込む必要があるため、これ以上の機能を実装したくありませんでした。

まあ、要件が変わったら、コードを変更する必要があります。ユニットテストを書かなかったら、あなたはあなたの仕事を終えなかったと言うでしょう。ただし、これは、単体テストで100%カバーする必要があるという意味ではありません。これは目標ではありません。いくつかのもの(GUIやファイルへのアクセスなど)は、単体テストすることさえ意図されていません。

この結果、コードの品質が向上し、テストがさらに強化されます。それだけの価値があると思います。


また、数千の受け入れテストがあり、すべてを実行するには1週間かかりました。

1
BЈовић