すべてのメソッドをテストする必要がありますか?


62

それで今日、チームメイトとユニットテストについて話しました。全体が始まったのは、彼が「ねえ、そのクラスのテストはどこにありますか、1つしか見えないのですか?」クラス全体がマネージャー(または、そのように呼び出したい場合はサービス)であり、ほとんどすべてのメソッドが単にものをDAOに委任しているため、次のようになりました。

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

ロジックを持たない種類のボイラープレート(または、少なくともロジックのような単純な委任を考慮していない)ですが、ほとんどの場合(レイヤー分離など)に便利なボイラープレートです。そして、ユニットテストを行うかどうかについてかなり長い議論がありました(DAOの完全なユニットテストを行ったことは言及する価値があると思います)。彼の主な議論は、TDDではないこと(明らかに)、誰かがこのメソッドが何をするかを確認するためにテストを見たいと思うかもしれない(それがどのようにもっと明白になる可能性があるのか​​わかりません)、または将来誰かが変更したいかもしれないということです実装し、それに新しい(または「より多く」のような)ロジックを追加します(この場合、誰かが単純にそのロジックをテストする必要があると思います)。

しかし、これは私に考えさせられました。最高のテストカバレッジ%を目指して努力する必要がありますか?それとも、それは単に芸術のための芸術ですか?私は、次のようなテストの背後にある理由をまったく見ていません。

  • ゲッターとセッター(実際に何らかのロジックがある場合を除く)
  • 「定型」コード

明らかに、このようなメソッド(モックを使用)のテストには1分もかかりませんが、それでも時間の無駄であり、CIごとに1ミリ秒長くなると思います。

コードのすべての(または彼ができる限り)行をテストする必要がある理由について、合理的/「可燃性でない」理由はありますか?


2
私はまだこの質問について決心していますが、答えは「いいえ」であると決めた人の話です。Ian Cooper:TDD、どこがうまくいかなかったのかこの素晴らしい話を要約するには、外部メソッドをテストし、新しいメソッドではなく新しい動作をテストする必要があります。
ダニエルカプラン14年

これは本当に素晴らしい話であり、必見であり、多くの人々にとって目を見張るような話であり、大好きです。しかし、答えは「ノー」ではないと思います。その「はい、しかし間接的に」。Ian cooperが六角形のアーキテクチャとポートのモック/スタブのテスト機能/動作について話します。この場合、このポートはDAOであり、この「マネージャー/サービス」は、このクラスのみの個別のユニットテストではなく、いくつかの機能をテストする「ユニットテスト」(Ian Cooper定義のユニット)でテストされますこのマネージャー/サービスを使用するドメイン内。
アルフレードカサド14年


システムにある程度依存します。中程度から最高レベルの安全認証を備えたシステムを開発している場合は、些細なことに関係なくすべての方法をカバーする必要があります
jk。

回答:


49

ケント・ベックの経験則で行きます:

破損する可能性のあるすべてをテストします。

もちろん、それはある程度主観的です。私にとって、上記のような些細なゲッター/セッターとワンライナーは、通常は価値がありません。しかし、繰り返しになりますが、ほとんどの時間をレガシーコードの単体テストの作成に費やし、素晴らしいグリーンフィールドTDDプロジェクトを夢見ているだけです。そのようなプロジェクトでは、ルールが異なります。レガシコードの主な目的は、できるだけ少ない労力でできるだけ多くの領域をカバーすることです。そのため、ユニットテストは、用語に専念している場合の統合テストのように、より高レベルでより複雑になる傾向があります。また、全体的なコードカバレッジを0%から上げることに苦労している場合、または25%を超えることができた場合、ゲッターとセッターの単体テストは心配する必要がありません。

グリーンフィールドTDDプロジェクトでのOTOHでは、そのような方法であってもテストを書くことはより現実的かもしれません。特に、「この1行は専用のテストに値するのか?」と疑問に思う前に、すでにテストを書いているので。そして、少なくともこれらのテストは書くのが簡単で実行が速いので、どちらの方法でも大したことではありません。


ああ、私はその引用を完全に忘れてしまいました!率直に言って、私はそれを私の主な議論として使用すると思います-ここで何が壊れるか?それほど多くはありません。中断できるのはメソッドの呼び出しだけであり、それが発生した場合、本当に悪いことが起こったことを意味します。ありがとう!

5
@Zenzen:「ここで何が壊れるのか?それほど多くない」-それで壊れることがあります。ちょっとしたタイプミス。または誰かがコードを追加します。または、依存関係を台無しにします。ベックは、あなたの主な例が壊れやすいものだと主張するだろうと本当に思います。ゲッターとセッターはそれほどではありませんが、それでもコピー/貼り付けエラーに巻き込まれています。本当の問題は、テストを書くのが簡単すぎる場合、なぜ存在するのでしょうか?
pdr

1
あなたが既にそれについて考えて過ごした時間は、あなたがテストを書いたかもしれません。私はテストを書くと言って、グレーの領域としてテストを書かないときは離れないでください、もっと壊れたウィンドウが表示されます。
kett_chup

1
私の一般的な経験では、ゲッターとセッターをテストすることは、長期的ではあるが優先度が低い場合にいくらか価値があることを付け加えます。理由は、バグを発見する可能性が「ゼロ」であるため、別の開発者が3か月以内に何かを追加しないことを保証できないためです(「単純なifステートメント」)。 。ユニットテストを実施することで、それを防ぐことができます。同時に、すぐに何かを見つけることはないので、それほど優先順位が高いわけではありません。
dclements

7
壊れる可能性のあるすべてを盲目的にテストすることは意味がありません。リスクの高いコンポーネントを最初にテストする戦略が必要です。
CodeART 14年

13

ユニットテストにはいくつかの種類があります。

  • 状態ベース。行動し、オブジェクトの状態に対してアサートします。例えば、私は預金をします。次に、残高が増えたかどうかを確認します。
  • 戻り値ベース。戻り値に対して行動し、主張します。
  • 相互作用ベース。オブジェクトが別のオブジェクトを呼び出したことを確認します。これはあなたの例でやっていることのようです。

データアクセスレイヤーを呼び出すと予想されるように、最初にテストを作成する場合、それはより理にかなっています。テストは最初に失敗します。次に、テストに合格するための製品コードを作成します。

理想的には論理コードをテストする必要がありますが、相互作用(他のオブジェクトを呼び出すオブジェクト)も同様に重要です。あなたの場合、私は

  • 渡された正確なパラメーターでデータアクセスレイヤーを呼び出したことを確認します。
  • 一度だけ呼び出されたことを確認してください。
  • データアクセスレイヤーから提供されたものを正確に返すことを確認します。それ以外の場合は、nullを返すこともあります。

現在、そこにはロジックはありませんが、常にそうであるとは限りません。

ただし、このメソッドにロジックがなく、同じままであると確信している場合は、コンシューマから直接データアクセスレイヤーを呼び出すことを検討します。チームの他のメンバーが同じページにいる場合にのみ、これを行います。チームに間違ったメッセージを送信したくない場合は、「やあ、ドメイン層を無視しても構いません。データアクセス層を直接呼び出してください」と言います。

また、このメソッドの統合テストがある場合は、他のコンポーネントのテストにも集中します。しかし、しっかりした統合テストを行っている会社はまだ見ていません。

このすべてを言った-私は盲目的にすべてをテストしません。ホットスポット(複雑性が高く、破損のリスクが高いコンポーネント)を確立します。次に、これらのコンポーネントに集中します。残りの10%がシステムのコアロジックを表し、複雑さのためにユニットテストでカバーされない場合、コードベースの90%が非常に単純で、ユニットテストでカバーされるコードベースを持つことは意味がありません。

最後に、この方法をテストすることの利点は何ですか?これが機能しない場合の影響は何ですか?彼らは壊滅的ですか?高いコードカバレッジを得るために努力しないでください。コードカバレッジは、優れた単体テストスイートの副産物でなければなりません。たとえば、ツリーをたどってこのメソッドを100%網羅するテストを1つ記述するか、100%カバレッジを実現する3つの単体テストを記述することができます。違いは、3つのテストを記述することで、ツリーをたどるのではなく、エッジケースをテストすることです。


DALが一度だけ呼び出されたことを確認するのはなぜですか?
マルジャンヴェネ

9

ソフトウェアの品質について考える良い方法は次のとおりです。

  1. 型チェックは問題の一部を処理しています。
  2. テストは残りを処理します

定型的な機能や些細な機能の場合は、タイプチェックを行うことでその役割を果たし、残りの部分ではテストケースが必要になります。


もちろん、型チェックは、コードで特定の型を使用している場合にのみ機能し、コンパイル済み言語を使用しているか、CIの一部として静的分析チェックが頻繁に実行されるようにします。
bdsl

6

私の意見では、循環的複雑度はパラメーターです。メソッドが十分に複雑でない場合(ゲッターやセッターなど)。単体テストは必要ありません。McCabeのCyclomatic Complexityレベルは1を超える必要があります。別の単語には、最低1つのブロックステートメントが必要です。


一部のゲッターまたはセッターには副作用があることを覚えておいてください(ほとんどの場合、推奨されず、悪い習慣と見なされます)。そのため、ソースコードの変更も影響する可能性があります。
アンジェイBobak

3

TDDを使用した圧倒的なYES(およびいくつかの例外を含む)

議論の余地はありますが、この質問に「いいえ」と答えた人は誰でもTDDの基本的な概念を欠いていると主張します。

私にとって、TDDに従うと答えはイエスです。そうでない場合は、もっともな答えはありません。

TDDのDDD

TDDは、主に3つの利点があると言われています。

  • 防衛
    • コードが確実に変更される可能性があります、動作は変更されません
    • これにより、リファクタリングの非常に重要なプラクティスが可能になります。
    • このTDDを獲得するかどうか。
  • 設計
    • あなたは指定し、それが振る舞うべきか、何をすべきか何か実装する前にそれを。
    • これは、多くの場合、より多くの情報に基づいた実装の決定を意味します。
  • ドキュメンテーション
    • テストスイートは、仕様(要件)のドキュメントとして機能する必要があります。
    • そのような目的でテストを使用するということは、ドキュメントと実装が常に一貫した状態にあることを意味します。一方への変更は、他方への変更を意味します。要件とデザインを別々のワードドキュメントに保持することと比較してください。

実装から責任を分離

プログラマーとして、属性を重要なものと考え、ゲッターとセッターを何らかのオーバーヘッドと考えるのは非常に魅力的です。

しかし、属性は実装の詳細であり、セッターとゲッターは実際にプログラムを機能させる契約上のインターフェースです。

オブジェクトがすべきことを綴ることははるかに重要です:

クライアントが状態を変更できるようにする

そして

クライアントがその状態を照会できるようにする

次に、この状態が実際に保存される方法(属性が最も一般的ですが、唯一の方法ではありません)。

などのテスト

(The Painter class) should store the provided colour

TDD のドキュメント部分にとって重要です。

最終的な実装が些細な(属性)であり、防御の利点をもたらさないという事実は、テストを作成するときに知らないはずです。

往復エンジニアリングの欠如...

システム開発の世界における主要な問題の1つは、ラウンドトリップエンジニアリング1の欠如です。 システムの開発プロセスはばらばらのサブプロセスに断片化され、その成果物(ドキュメント、コード)はしばしば矛盾しています。

1 Brodie、Michael L.「ジョンミロポロス:概念モデリングの種を縫う」概念モデリング:基礎とアプリケーション。スプリンガーベルリンハイデルベルク、2009年1-9。

...そしてTDDがそれをどのように解決するか

システムとそのコードの仕様が常に一貫していることを保証するのは、TDD のドキュメント部分です。

最初に設計し、後で実装する

TDD内では、失敗した受け入れテストを最初に記述し、次に合格するコードを記述します。

上位レベルのBDD内で、最初にシナリオを作成し、次にそれらを通過させます。

セッターとゲッターを除外する必要があるのはなぜですか?

理論的には、TDD内で1人がテストを記述し、もう1人がテストをパスするコードを実装することは完全に可能です。

自問してください:

クラスのテストを書いている人がゲッターとセッターに言及する必要があります。

ゲッターとセッターはクラスへのパブリックインターフェイスであるため、答えは明らかにyesであるか、オブジェクトの状態を設定または照会する方法はありません。

明らかに、最初にコードを記述した場合、答えはそれほど明確ではないかもしれません。

例外

このルールにはいくつかの明らかな例外があります-実装の詳細は明確であり、明らかにシステムの設計の一部ではない関数です。

たとえば、ローカルメソッド 'B()':

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

またはsquare()ここのプライベート関数:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

またはpublic、システムコンポーネントの設計でスペルを必要とするインターフェイスの一部ではないその他の機能。


1

哲学的な質問に直面したとき、運転の要件に戻ってください。

合理的な信頼性のあるソフトウェアを競争力のあるコストで生産することがあなたの目標ですか?

それとも、ほとんどコストに関係なく、可能な限り最高の信頼性のソフトウェアを生産することですか?

品質と開発速度/コストの2つの目標は、ある程度までは、欠陥を修正するよりもテストの作成に費やす時間が少なくなります。

しかし、そのポイントを超えて、彼らはしません。たとえば、開発者ごとに1か月に1つのバグを報告するのはそれほど難しくありません。これを2か月に1回に半分にすると、おそらく1〜2日の予算しか解放されず、余分なテストを行っても欠陥率は半分になりません。したがって、もはや単純なwin / winではありません。顧客への欠陥コストに基づいて正当化する必要があります。

このコストは変化します(そして、悪になりたいのであれば、市場や訴訟を通じて、それらのコストを強制する能力も変わります)。悪になりたくないので、それらのコストを完全に数えます。時々、いくつかのテストは、それらの存在によって世界的にまだ世界をより貧しくします。

要するに、旅客の旅客機のフライトソフトウェアと同じ基準を社内のWebサイトに盲目的に適用しようとすると、結局は廃業または刑務所に入ることになります。


0

これに対するあなたの答えはあなたの哲学に依存します(シカゴ対ロンドンだと思いますか?誰かが調べてくれると思います)。審査員は、最も時間効率の良いアプローチでこれにまだ取り組んでいます(なぜなら、これが修正に費やす時間の最大の要因だからです)。

パブリックインターフェイスのみをテストするというアプローチもあれば、すべての関数で各関数呼び出しの順序をテストするというアプローチもあります。多くの聖戦が戦われました。私のアドバイスは、両方のアプローチを試すことです。コードの単位を選択してXのように実行し、別のコードをYのように実行します。数か月のテストと統合の後、どれがニーズに合っているかを確認します。


0

難しい質問です。

厳密に言えば、それは必要ではないと言うでしょう。ポジティブおよびネガティブなシナリオで意図したとおりにビジネス要件が機能することを保証するBDDスタイルのユニットおよびシステムレベルのテストを作成する方が適切です。

メソッドがこれらのテストケースでカバーされていない場合、そもそもなぜ存在するのか、それが必要なのか、ドキュメントやユーザーストーリーに反映されないコードに隠された要件があるのか​​を疑問視する必要がありますBDDスタイルのテストケースでエンコードする必要があります。

個人的には、ラインごとのカバレッジを約85〜95%に維持し、メインラインへのゲートチェックインを行い、すべてのコードファイルで既存のユニットテストカバレッジがこのレベルに達するようにします。

最良のテストプラクティスが守られていると仮定すると、これは開発者に、実行するのが難しいコードまたは単純なコードで追加のカバレッジを取得する方法を見つけ出すために時間を費やすことなく、十分なカバレッジを提供します。


-1

問題は質問そのものです。システムのすべての機能をテストするために必要なすべての「メトード」またはすべての「クラス」をテストする必要はありません。

メソッドやクラスの観点で考えるのではなく、機能/動作の観点での重要な考え方。もちろん、1つまたは複数の機能のサポートを提供するためのメソッドがあり、最後にすべてのコードがテストされ、少なくともすべてのコードがコードベースで重要になります。

あなたのシナリオでは、おそらくこの「マネージャー」クラスは冗長または不必要です(「マネージャー」という単語を含む名前を持つすべてのクラスのように)、またはそうでないかもしれませんが、実装の詳細のようです、おそらくこのクラスはユニットに値しませんこのクラスには関連するビジネスロジックがないため、テストします。おそらく、いくつかの機能を動作させるためにこのクラスが必要です。この機能のテストはこのクラスをカバーします。この方法でこのクラスをリファクタリングし、重要なものである機能がリファクタリング後も動作することを確認するテストを行うことができます。

メソッドクラスではなく機能/動作で考えてください。これを十分に繰り返すことはできません。


-4

しかし、これは私に考えさせられました。最高のテストカバレッジ%を目指して努力する必要がありますか?

はい、理想的には100%ですが、一部の項目はユニットテストできません。

ゲッターとセッター(実際に何らかのロジックがある場合を除く)

ゲッター/セッターは愚かです -使用しないでください。代わりに、メンバー変数をパブリックセクションに配置します。

「定型」コード

一般的なコードを取得し、単体テストを行います。それはそれと同じくらい簡単でなければなりません。

コードのすべての(または彼ができる限り)行をテストする必要がある理由について、合理的/「可燃性でない」理由はありますか?

そうしないと、非常に明らかなバグを見逃す可能性があります。単体テストは、特定の種類のバグを検出するためのセーフネットのようなものであり、可能な限り使用する必要があります。

そして最後のこと:私は人々がいくつかの「単純なコード」のためのユニットテストを書くのに時間を使いたくないプロジェクトにいますが、後で彼らはまったく書かないことを決めました。最後に、コードの一部が泥の大きな玉になりました。


さて、1つわかります。TDD/書き込みテストを使用しないという意味ではありませんでした。まったく逆です。テストでは考えていないバグが見つかる可能性があることは知っていますが、ここでテストするものは何ですか?私はそのような方法が「単体テスト不可能」な方法の1つであると単に考えています。PéterTörökが言ったように(Kent Beckを引用)、壊れる可能性のあるものをテストする必要があります。ここで何が壊れる可能性がありますか?それほど多くはありません(このメソッドには単純な委任しかありません)。私は単体テストを書くことができますが、DAOのモックとアサートがありますが、テストはそれほど多くありません。ゲッター/セッターに関しては、いくつかのフレームワークがそれらを必要とします。

1
また、「一般的なコードを取り出して単体テストを行います。それはそれと同じくらい簡単なはずです。」それはどういう意味ですか?これはサービスクラス(GUIとDAOの間のサービスレイヤー)であり、アプリ全体に共通です。実際には、より汎用的にすることはできません(いくつかのパラメーターを受け入れ、DAOの特定のメソッドを呼び出すため)。GUIがDAOを直接呼び出さないように、アプリケーションの階層化アーキテクチャに準拠することが唯一の理由です。
Zenzen

20
-1のための「ゲッター/セッターは愚かです-ちょうどそれらを使用しない代わりに、パブリックセクションに、あなたのメンバ変数を置きます。。」- 大きな過ち。これはSOで何度か 議論されています。パブリックフィールドをどこでも使用することは、実際にゲッターとセッターをどこで使用するよりも悪いです。
ペテルトーロク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.