web-dev-qa-db-ja.com

ユニットテストが多すぎるということはありますか?

私は、既存のアプリケーションの単体テストの作成を任されています。最初のファイルを完成させた後、元のコードの419行に対して717行のテストコードがあります。

コードカバレッジを増やすと、この比率は管理できなくなりますか?

単体テストについての私の理解は、クラス内の各メソッドをテストして、すべてのメソッドが期待どおりに機能することを確認することでした。ただし、プルリクエストで、私の技術リーダーは、より高いレベルのテストに集中する必要があると述べました。彼は、各関数を徹底的にテストするのではなく、問題のクラスで最も一般的に使用される4-5のユースケースをテストすることを提案しました。

技術リーダーのコメントを信頼しています。彼は私よりも多くの経験があり、ソフトウェアの設計に関しては本能が優れています。しかし、多人数のチームがこのようなあいまいな標準のテストをどのように作成するのでしょうか。つまり、どのようにして同僚を知ることができ、「最も一般的な使用例」について同じ考えを共有しますか?

私には、100%の単体テストカバレッジは高い目標ですが、50%に達しただけでも、その50%の100%がカバーされたことがわかります。それ以外の場合、各ファイルの一部のテストを作成すると、だまされる余地がたくさん残ります。

143
user2954463

はい、カバレッジが100%の場合、不要なテストを記述します。残念ながら、不要なテストを特定するための信頼できる唯一の方法は、それらすべてを記述してから、10年程度待って、失敗していないテストを確認することです。

多くのテストを維持することは通常問題にはなりません。多くのチームは、100%の単体テストカバレッジに加えて、統合とシステムテストを自動化しています。

ただし、テストメンテナンスフェーズではないため、追いついています。 100%のテストカバレッジで50%のクラスを100%のクラスでテストするよりも、クラスの100%をテストするほうがはるかに優れています。そのベースラインを取得した後、次のステップは通常、今後変更されるファイルを100%プッシュすることです。

180
Karl Bielefeldt

テスト駆動開発を使用して作成された大規模なコードベースに取り組んだことがあれば、ユニットテストが多すぎるなどの事態が発生する可能性があることはすでにご存じでしょう。場合によっては、開発作業のほとんどは、実行時に関連するクラスの不変条件、事前条件、および事後条件のチェックとして最適に実装される低品質のテストの更新で構成されます(つまり、より高いレベルのテストの副作用としてのテスト) )。

もう1つの問題は、カーゴカルト主導の設計手法を使用して質の低い設計を作成することです。これにより、テストするものが増えます(クラス、インターフェースなど)。この場合、負担はテストコードの更新であるように見えるかもしれませんが、真の問題は質の低い設計です。

68
Frank Hileman

あなたの質問への回答

ユニットテストが多すぎるということはありますか?

確かに...たとえば、一見異なるように見えるが実際には同じものをテストする複数のテストを行うことができます(テスト対象の「興味深い」アプリケーションコードの同じ行に論理的に依存します)。

または、外部に表面化しない(つまり、どのような種類のインターフェイスコントラクトの一部でもない)コードの内部をテストすることもできます。たとえば、内部ログメッセージの正確な表現など。

私は、既存のアプリケーションの単体テストの作成を任されています。最初のファイルを完成させた後、元のコードの419行に対して717行のテストコードがあります。

それは私にはかなり普通のことです。テストでは、実際のテストに加えて、セットアップとティアダウンに多くのコード行を費やしています。比率は改善する場合と改善しない場合があります。私自身はかなりテストが重いので、実際のコードよりもテストにl-o-cと時間を費やすことがよくあります。

コードカバレッジを増やすと、この比率は管理できなくなりますか?

比率はそれほど考慮に入れません。テストを管理不能にする傾向があるテストの他の品質があります。コードでかなり単純な変更を行うときに、一連のテスト全体を定期的にリファクタリングする必要がある場合は、その理由をよく調べる必要があります。そして、それらはあなたが持っている行の数ではなく、テストのコーディングにどのように取り組むかです。

単体テストについての私の理解は、クラス内の各メソッドをテストして、すべてのメソッドが期待どおりに機能することを確認することでした。

これは、厳密な意味での「単体」テストに適しています。ここで、「ユニット」はメソッドやクラスのようなものです。 「ユニット」テストのポイントは、システム全体ではなく、1つの特定のコードユニットのみをテストすることです。理想的には、残りのシステム全体を削除します(doubleまたはwhatnotを使用)。

ただし、プルリクエストで、私の技術リーダーは、より高いレベルのテストに集中する必要があると述べました。

次に、人々が実際にmeantユニットテストを想定しているという罠に陥りましたsaid単体テスト。 「単体テスト」と言っても、まったく違うことを意味する多くのプログラマーに会いました。

彼は、各関数を徹底的にテストするのではなく、問題のクラスで最も一般的に使用される4-5のユースケースをテストすることを提案しました。

もちろん、重要なコードの上位80%に集中することで、負荷も軽減されます...上司を高く評価していることを感謝しますが、これは最適な選択ではありません。

私には、100%の単体テストカバレッジは高い目標ですが、50%に達しただけでも、その50%の100%がカバーされたことがわかります。

「単体テストカバレッジ」が何であるかわかりません。 「コードカバレッジ」を意味すると思います。つまり、テストスイートを実行した後、コードのすべての行(= 100%)が少なくとも1回実行されています。

これはニースの球場測定基準ですが、はるかに優れた標準ではありません。コード行を実行するだけでは全体像がわかりません。これは、たとえば、複雑なネストされたブランチを通るさまざまなパスを考慮に入れていません。これは、テストが少なすぎるコードの断片に指を向けるより詳細なメトリックです(明らかに、クラスが10%または5%のコードカバレッジの場合、何かが間違っています)。一方、100%のカバレッジでは、十分にテストしたか、正しくテストしたかはわかりません。

統合テスト

デフォルトで今日unitテストについて人々が絶えず話していると、それは私をかなり困らせます。私の意見(および経験)では、unitテストはライブラリ/ APIに最適です。よりビジネス指向の領域(当面の質問のようなユースケースについて話し合う領域)では、それらは必ずしも最良の選択肢ではありません。

一般的なアプリケーションコードおよび平均的なビジネス(お金を稼ぐ、納期を守る、顧客満足を満たすことが重要である)の場合、主に直接ユーザーの顔にある、またはリアル災害-ここではNASAのロケット打ち上げについて話しているわけではありません)、統合または機能テストははるかです有用。

それらは振る舞い駆動型開発または機能駆動型開発と密接に関連しています。定義上、これらは(厳密な)単体テストでは機能しません。

簡潔に保つために、統合/機能テストはアプリケーションスタック全体を実行します。 Webベースのアプリケーションでは、ブラウザがアプリケーションをクリックするように動作します(そして、いいえ、明らかに単純ではありませんhaveしません)それを行うための非常に強力なフレームワークがあります-チェックアウト http://cucumber.io 例を参照してください)。

ああ、最後の質問に答えるために:新しい機能が機能テストが実装されて失敗した後にのみプログラムされるようにすることで、チーム全体が高いテストカバレッジを得ることができます。そして、はい、それはevery機能を意味します。この保証は100%(ポジティブ)の機能カバレッジです。定義上、アプリケーションの機能が「なくなる」ことはありません。 100%のコードカバレッジを保証するものではありません(たとえば、否定的な機能を積極的にプログラミングしない限り、エラー処理/例外処理を実行することはありません)。

バグのないアプリケーションを保証するものではありません。もちろん、明らかな、または非常に危険なバグのある状況、誤ったユーザー入力、ハッキング(たとえば、周囲のセッション管理、セキュリティなど)などの機能テストを作成する必要があります。しかし、ポジティブテストのプログラミングだけでも多大な利点があり、最新の強力なフレームワークで非常に実現可能です。

機能/統合テストには、独自のワーム缶があることは明らかです(たとえば、パフォーマンス、サードパーティフレームワークの冗長テスト、通常はダブルを使用しないため、私の経験では、それらを書くのが難しくなる傾向があります...)が、私はd 100%のポジティブ機能でテストされたアプリケーションを、100%のコードカバレッジユニットでテストされたアプリケーション(ライブラリではありません!)よりもいつでも取得します。

36
AnoE

はい、単体テストが多すぎる可能性があります。たとえば、単体テストで100%カバレッジがあり、統合テストがない場合は、明確な問題があります。

いくつかのシナリオ:

  1. テストを特定の実装にオーバーエンジニアリングします。次に、リファクタリング時に単体テストを破棄する必要があります。実装を変更したときは言うまでもありません(パフォーマンスの最適化を実行するときに非常に頻繁に発生する問題)。

    単体テストと統合テストのバランスが適切であれば、大幅なカバレッジを失うことなくこの問題を軽減できます。

  2. 所有しているテストの20%ですべてのコミットを適切にカバーし、残りの80%を統合または少なくとも個別のテストパスに残すことができます。このシナリオで見られる主なマイナスの影響は、テストの実行に長い時間を待たなければならないため、変化が遅いことです。

  3. コードを変更しすぎてテストできない。たとえば、コンポーネントを変更する必要がないIoCの悪用をたくさん見ましたが、少なくともそれらを一般化することはコストが高く、優先度が低いですが、人々はそれらを一般化してリファクタリングし、ユニットテストを行うために多くの時間を費やしています。 。

私は特に、ファイルの50%を100%カバーするのではなく、100%のファイルを50%カバーするという提案に同意します。最も一般的なポジティブケースと最も危険なネガティブケースに最初の努力を集中します。エラー処理と異常なパスにあまり投資しないでください。重要ではないためではなく、限られた時間と無限のテストユニバースがあるためです。したがって、どのような場合でも優先順位を付ける必要があります。

25
Bruno Guardia

各テストにはコストと利点があります。欠点は次のとおりです。

  • テストを書く必要があります。
  • テストの実行には(通常はごくわずか)時間かかります。
  • コードでテストを維持する必要があります。テストしているAPIが変更された場合、テストを変更する必要があります。
  • テストを作成するために、デザインを変更する必要がある場合があります(ただし、これらの変更は通常は改善されます)。

コストがメリットを上回る場合は、テストを記述しない方がよいでしょう。たとえば、機能のテストが難しい場合、APIは頻繁に変更され、正確性は比較的重要ではなく、テストで欠陥を見つける可能性が低い場合は、おそらくそれを記述しない方がよいでしょう。

テストとコードの特定の比率については、コードが十分にロジック密度が高い場合、その比率が保証されます。ただし、通常のアプリケーション全体でこのような高い比率を維持する価値はありません。

19

はい、ユニットテストが多すぎるなどの問題があります。

テストは優れていますが、すべての単体テストは次のとおりです。

  • APIと密接に関連している潜在的なメンテナンスの負担

  • 他の何かに費やすことができる時間

  • 単体テストスイートの時間のスライス
  • 他のテストに合格してこのテストが失敗する可能性が非常に低い他のいくつかのテストの複製であるため、実際の値は追加されない可能性があります。

100%のコードカバレッジを目指すのが賢明ですが、これは、特定のエントリポイント(関数/メソッド/呼び出しなど)でそれぞれが100%のコードカバレッジを提供する一連のテストを意味します。

十分なカバレッジを達成してバグを排除するのがどれほど難しいかを考えると、「ユニットテストが間違っている」だけでなく「ユニットテストが多すぎる」などの問題が発生している可能性があります。

ほとんどのコードの語用論は次のことを示しています。

  1. エントリポイントのカバレッジが100%(すべてが何らかの形でテストされる)であることを確認し、「非エラー」パスのコードカバレッジが100%に近づくことを目指します。

  2. 関連する最小/最大値またはサイズをテストします

  3. 奇妙な特別なケース、特に「奇数」の値だと思うものは何でもテストしてください。

  4. バグを見つけたら、そのバグを明らかにしたユニットテストを追加し、同様のケースを追加する必要があるかどうかを検討します。

より複雑なアルゴリズムについては、以下も考慮してください。

  1. より多くのケースのいくつかのバルクテストを行います。
  2. 結果を「総当たり」の実装と比較し、不変条件をチェックします。
  3. ランダムなテストケースを作成し、ブルートフォースや不変条件を含む事後条件をチェックするいくつかの方法を使用します。

たとえば、ランダム化された入力を使用して並べ替えアルゴリズムをチェックし、データをスキャンして最後にデータが並べ替えられていることを確認します。

あなたの技術リーダーは「最小限の裸の尻」テストを提案していると思います。私は「最高値の品質テスト」を提供しており、その間にスペクトルがあります。

たぶん、あなたの先輩はあなたが構築しているコンポーネントがいくつかのより大きな部分に埋め込まれ、統合されたときにより徹底的にユニットテストされることを知っているでしょう。

重要な教訓は、バグが見つかったときにテストを追加することです。ユニットテストの開発に関する私の最高のレッスンを私に導きます:

サブユニットではなくユニットに焦点を当てます。サブユニットからユニットを構築している場合は、サブユニットを非常に基本的なテストを記述して、サブユニットを制御ユニットを通じてテストすることにより、それらがもっともらしく、カバレッジを向上させます。

したがって、コンパイラを作成していて、シンボルテーブルを作成する必要がある場合(たとえば)。シンボルテーブルを起動して基本的なテストを実行し、テーブルを埋める宣言パーサーに(たとえば)取り組みます。シンボルテーブルの「スタンドアロン」ユニットにバグが見つかった場合にのみ、テストを追加します。それ以外の場合は、宣言パーサーおよび後でコンパイラ全体の単体テストによるカバレッジを増やします。

これは費用対効果が高く(全体の1つのテストは複数のコンポーネントのテストです)、より安定しがちなテストでは「外部」インターフェイスのみが使用されるため、再設計と改良の余地が残ります。

すべてのレベルでの不変条件を含むデバッグコードテストの前提条件、事後条件を組み合わせることで、最小限のテスト実装で最大のテストカバレッジを得ることができます。

13
Persixty

まず、本番コードよりもlinesのテストを多く行うことは必ずしも問題ではありません。テストコードは直線的である(またはそうでなければならない)-理解しやすい-必要な複雑さは、量産コードがそうであるかどうかにかかわらず、非常に低いです。テストのcomplexityが製品コードのそれに近づき始めた場合は、おそらく問題があります。

はい、ユニットテストが多すぎる可能性があります。単純な思考実験では、追加の値を提供しないテストを追加し続けることができ、追加されたすべてのテストが少なくともいくつかのリファクタリングを阻害する可能性があることを示しています。

私の意見では、最も一般的なケースのみをテストするというアドバイスには欠陥があります。これらはシステムテスト時間を節約するためのスモークテストとして機能する場合がありますが、本当に価値のあるテストは、システム全体で実行するのが難しいケースを検出します。たとえば、メモリ割り当てエラーの制御されたエラー挿入を使用すると、完全に未知の品質である可能性のあるリカバリパスを実行できます。または、除数として使用されることがわかっている値(または平方根になる負の数)としてゼロを渡し、未処理の例外が発生しないことを確認します。

次に最も価値のあるテストは、極限または境界点を行使するテストです。たとえば、(1ベースの)月を受け入れる関数は、0、1、12、および13でテストする必要があります。これにより、有効/無効の遷移が適切な場所にあることがわかります。これらのテストに2..11を使用することも過剰テストです。

既存のコードのテストを作成する必要があるという点で、あなたは難しい立場にいます。コードを記述している(または記述しようとしている)ので、Edgeケースを特定するのは簡単です。

3
Toby Speight

単体テストについての私の理解は、クラス内の各メソッドをテストして、すべてのメソッドが期待どおりに機能することを確認することでした。

この理解は間違っています。

単体テストはテスト中のユニット動作を検証します。

その意味で、nitは必ずしも「クラス内のメソッド」ではありません。 The Art of Unit TestingでのRoy Osheroveによるユニットの定義が好きです。

ユニットは、変更する理由が同じであるすべての製品コードです。

これに基づいて、単体テストはコードのすべての望ましい動作を検証する必要があります。 「欲望」が多かれ少なかれ要件から取られている場合。


ただし、プルリクエストで、私の技術リーダーは、より高いレベルのテストに集中する必要があると述べました。

彼は正しいが、彼が思っているのとは異なる方法で。

あなたの質問から、あなたはそのプロジェクトの「専用テスター」であると私は理解しています。

大きな誤解は、彼がユニットテストを書くことを期待していることです(「ユニットテストフレームワークを使用したテスト」とは対照的です)。 ynitテストを書くことは開発者の責任であり、テスターではありません(理想的な世界では、私は知っています...)。一方、あなたはこの質問にTDDのタグを付けました。

テスターとしてのあなたの仕事は、モジュールやアプリケーションのテストを書く(または手動で実行する)ことです。また、この種のテストでは、主にすべてのユニットがスムーズに連携することを確認する必要があります。つまり、各ユニットが少なくとも1回実行されるになるようにテストケースを選択する必要があります。そして、そのチェックが実行されます。実際の結果は将来の要件によって変更される可能性があるため、それほど重要ではありません。

ダンプ自動車のアナロジーをもう一度強調するには:組立ラインの終わりに車でいくつのテストが行​​われますか?正確に1つ:駐車場まで自分で運転する必要があります...

ここでのポイントは次のとおりです。

「単体テスト」と「単体テストフレームワークを使用して自動化されたテスト」の違いに注意する必要があります。


私には、100%の単体テストカバレッジは高い目標ですが、50%に達しただけでも、その50%の100%がカバーされたことがわかります。

単体テストは安全策です。彼らはあなたに自信を与えるリファクタリングすでに実装されている動作を壊すことを恐れずに、技術的負債を減らしたり、新しい動作を追加したりするコード.

100%のコードカバレッジは必要ありません。

ただし、100%の動作範囲が必要です。 (はい、コードカバレッジと動作カバレッジは何らかの形で相互に関連していますが、そのため同じではありません。)

動作カバレッジが100%未満の場合、テストされていない動作の一部を変更した可能性があるため、テストスイートの実行が成功しても何も意味がありません。そして、あなたのリリースがオンラインになった翌日にあなたはあなたのクライアントによって気づかれるでしょう...


結論

いくつかのテストは、テストなしよりも優れています。間違いない!

しかし、単体テストが多すぎるようなことはありません。

これは、各ユニットテストがコード単一の期待値についてコード動作を検証するためです。また、コードに期待する以上の単体テストを作成することはできません。そして、安全ハーネスの穴は、不要な変更が生産システムに害を及ぼす可能性があります。

3
Timothy Truckle

もちろん。私は以前、大規模なソフトウェア会社のSDETでした。私たちの小さなチームは、以前ははるかに大きなチームで処理されていたテストコードを維持する必要がありました。その上、私たちの製品にはいくつかの依存関係があり、絶え間なく重大な変更が発生していました。これは、絶え間ないテスト保守を意味します。チームのサイズを大きくすることはできなかったため、価値の低い何千ものテストが失敗した場合は破棄する必要がありました。そうしないと、欠陥に追いつくことができません。

これを単なる管理上の問題として却下する前に、現実世界の多くのプロジェクトは、レガシーステータスに近づくにつれて人員の削減に苦しんでいることを考慮してください。時にはそれは最初のリリースの直後にも起こり始めます。

2
mrog

テストコードの行数が製品コードよりも多いことは、コピーアンドペーストを排除するためにテストコードをリファクタリングしていることを前提として、必ずしも問題ではありません。

問題は、ビジネスの意味を持たない、実装のミラーであるテストがあることです。たとえば、モックとスタブがロードされ、メソッドが他のメソッドを呼び出すことだけをアサートするテストです。

"ほとんどの単体テストが無駄である理由" 論文のすばらしい引用は、単体テストに「広く、正式で、独立したOracleの正しさ、そして...見積り可能なビジネス価値」がなければならないということです。

1
wrschneider

私が言及しなかったことの1つは、開発者がいつでも実行できるように、テストをquick and easyにする必要があることです。

テストが完了して変更が何かを壊したかどうかを確認する前に、ソース管理にチェックインして1時間以上(コードベースのサイズによって異なります)待つ必要はありません。ソース管理にチェックインする前に(または、少なくとも、変更をプッシュする前に)自分のマシン。理想的には、単一のスクリプトまたはボタンのプッシュでテストを実行できる必要があります。

また、これらのテストをローカルで実行する場合、数秒程度で高速に実行する必要があります。遅くなると、十分にまたはまったく実行しないように誘惑されます。

そのため、それらを実行するのに非常に多くのテストを実行するのに数分かかる場合や、過度に複雑なテストをいくつか実行する場合は、問題になる可能性があります。

0
mmathis