単体テストの深さはどれくらいですか?


88

TDDについて私が見つけたのは、テストをセットアップするのに時間がかかることと、自然に怠惰であることです。私が最初に行うことは、コンストラクターがすべてのプロパティを設定したことをテストすることですが、これはやりすぎですか?

私の質問は、ユニットテストをどの程度の粒度で作成することですか。

..そして、テストしすぎるケースはありますか?

回答:


221

テストではなく機能するコードに対して支払いを受けるので、私の哲学は、特定のレベルの信頼性に到達するためにできるだけテストしないことです(このレベルの信頼性は業界標準と比較して高いと思いますが、それはただのやり過ぎかもしれません)。 。通常、ある種の間違い(コンストラクターでの間違った変数の設定など)を行わない場合は、テストしません。私はテストエラーを理解する傾向があるので、複雑な条件付きのロジックがある場合は特に注意が必要です。チームでコーディングするときは、全体として間違ってしまう傾向があるコードを注意深くテストするように戦略を変更します。

この哲学に基づくテスト戦略は人によって異なりますが、テストがコーディングの内部ループにどのように最適に適合するかについての理解が未熟であることを考えると、それは理にかなっています。今から10〜20年後には、どのテストを作成し、どのテストを作成しないか、どのように違いを区別するかについて、より普遍的な理論が確立されるでしょう。それまでの間、実験は順調に進んでいるようです。


40
世界はケントベックがこれを言うとは思わない!ケントベックがすることだと思って100%のカバー率を忠実に追求している開発者の大群があります!XPの本で、あなたはいつもTest Firstを忠実に守っているとは限らないと言ったと多くの人に話しました。しかし、私も驚いています。
チャーリー花

6
開発者が作成したコードは彼自身のものではないため、実際には同意できません。次のスプリントは、他の誰かがそれを変更して、「知らない」というエラーをコミットします。TDDも最初にテストについて考えます。したがって、コードの一部をテストすると仮定してTDDを実行すると、それは間違っています
Ricardo Rodrigues

2
取材には興味がありません。ベック氏が失敗したテストに対応して書かれていないコードをコミットする頻度に非常に興味があります。
sheldonh

1
@RicardoRodrigues、他の人が後で書くコードをカバーするテストを書くことはできません。それは彼らの責任です。
キエフ

2
それは私が書いたものではありません。注意深く読んでください。あなたがあなた自身のコードの一部のみをカバーするテストを書く場合、「あなたが間違いを犯さないことがわかっている」というカバーされていない部分を残し、それらの部分が変更されて適切なテストがないと、そこに問題があります。それはTDDではありません。
Ricardo Rodrigues

20

壊れると予想されるもの、およびエッジケースの単体テストを記述します。その後、バグレポートが届いたら、バグの修正を書く前にテストケースを追加する必要があります。これにより、開発者は次のことを確信できます。

  1. バグが修正されました。
  2. バグは再発しません。

添付されたコメントによると-長期にわたって多くのバグが特定のクラスで発見された場合、ユニットテストを作成するこのアプローチは問題を引き起こす可能性があると思います。これはおそらく、裁量が役立つ場所です-再発する可能性のあるバグのみの単体テストを追加するか、それらの再発により深刻な問題が発生する場合です。ユニットテストでの統合テストの測定は、これらのシナリオで役立つことがわかりました。上位のコードパスのコードをテストすると、下位のコードパスをカバーできます。


私が書くバグの量によって、これはアンチパターンになる可能性があります。何かが壊れているコードで何百ものテストがあると、これはテストが読めなくなることを意味し、それらのテストを書き直すときになるとオーバーヘッドになる可能性があります。
Johnno Nolan

@JohnNolan:テストの読みやすさはそれほど重要ですか?私見ではありませんが、少なくともこれらのバグ固有の回帰テストではそうではありません。テストを頻繁に書き換えている場合は、テストのレベルが低すぎる可能性があります-理想的には、実装が変更されてもインターフェイスは比較的安定している必要があり、インターフェイスレベルでテストする必要があります(ただし、現実の世界では、そのように...:-/)インターフェイスが大幅に変更される場合、これらのバグ固有のテストのほとんどまたはすべてを書き直すよりも破棄することをお勧めします。
j_random_hacker 2012年

@j_random_hackerはい、もちろん読みやすさが重要です。テストはドキュメントの一種であり、製品コードと同じくらい重要です。大きな変更のテストを破棄することは良いことであり、テストはインターフェイスレベルで行う必要があることに同意します。
Johnno Nolan

19

すべてをできるだけ単純にする必要がありますが、単純にする必要はありません。-A.アインシュタイン

TDDについて最も誤解されていることの1つは、その最初の言葉です。テスト。そのため、BDDが登場しました。最初のDが重要なもの、つまりDrivenであることを人々は本当に理解していなかったからです。私たちは皆、テストについて少しずつ、そしてデザインの推進について少しずつ考える傾向があります。そして、これはあなたの質問に対する漠然とした答えだと思いますが、実際にテストしているものではなく、コードの駆動方法を検討する必要があります。これは、カバレッジツールが役立ちます。デザインはかなり大きく、問題の多い問題です。


ええ、それは漠然としています...これは、コンストラクターが一部の動作ではないため、テストすべきではないという意味ですか?しかし、私はMyClass.DoSomething()をテストする必要がありますか?
Johnno Nolan

まあ、:Pに依存します...レガシーコードをテストする場合、多くの場合、コンストラクションテストは良いスタートです。しかし、私は(ほとんどの場合)何かをゼロから設計し始めるとき、おそらく建設テストを省くでしょう。
kitofr 2008年

これは、駆動型の設計ではなく、駆動型の開発です。つまり、機能するベースラインを取得し、機能を検証するためのテストを記述し、開発を進めます。ほとんどの場合、コードを初めてファクタリングする直前にテストを作成します。
Evan Plaice

最後のD、デザインは人々が忘れる言葉で、焦点を失っていると思います。テスト駆動設計では、失敗したテストに対応してコードを記述します。テスト駆動型の設計を行っている場合、テストされていないコードはどれくらいになりますか?
sheldonh

15

「すべて」をテストすることを提案する人へ:のようなメソッドを「完全にテスト」するにint square(int x)は、一般的な言語と一般的な環境で約40億のテストケースが必要であることを認識してください。

実際には、それよりもさらに悪いことだ:メソッドをvoid setX(int newX)も義務付けられていないほか、他のメンバーの値を変更するx-あなたはそれをテストしているobj.yobj.zすべての呼び出し後に変更されないままなど、obj.setX(42);

「すべて」のサブセットをテストすることは実際的です。 これを受け入れると、信じられないほどの基本的な動作をテストしないことを検討するほうが口当たりが良くなります。すべてのプログラマーは、バグの場所の確率分布を持っています。スマートなアプローチは、バグの確率が高いと推定されるテスト領域にエネルギーを集中させることです。


9

古典的な答えは「壊れる可能性のあるものをすべてテストする」です。私はそれを、setまたはget以外に何もしないセッターおよびゲッターをテストすることはおそらくあまりにも多くのテストであり、時間をかける必要がないことを意味すると解釈します。あなたのIDEがあなたのためにそれらを書いていない限り、あなたもそうかもしれません。

プロパティを設定しないコンストラクターが後でエラーにつながる可能性がある場合、それらが設定されていることをテストしても過剰ではありません。


うん、これは多くのプロパティと多くのコンストラクタを持つクラスのバインドです。
Johnno Nolan

(メンバーをゼロに初期化するのを忘れるなど)問題が些細なほど、デバッグに時間がかかります。
レフ

5

作成するクラスの前提条件をカバーするテストを作成します。テストは要件を実施します。基本的に、たとえば、xが3になることはない場合、その要件をカバーするテストがあることを確認します。

必ず、条件をカバーするテストを記述しないと、後で「人間の」テスト中に発生します。私は確かにそれを書くつもりですが、私はむしろそれらを早く捕まえたいです。重要なのは、テストは(おそらく)面倒なことですが、必要なことです。私は完了するのに十分なテストを書いていますが、それ以上ではありません。


5

単純なテストをスキップする際の問題の一部は、将来的にはリファクタリングにより、その単純なプロパティが多くのロジックで非常に複雑になる可能性があります。モジュールの要件を検証するためにテストを使用できるというのが最良のアイデアだと思います。Xを渡したときにYを取り戻す必要がある場合は、それをテストします。次に、後でコードを変更するときに、XがYを提供することを確認でき、Aのテストを追加してBを提供できます(その要件が後で追加された場合)。

最初の開発中にテストを記述していた時間は、最初または2番目のバグ修正で報われました。3か月間見ていないコードを拾い上げ、修正がすべてのケースをカバーし、「おそらく」何も壊さないことを合理的に確認できる機能は、非常に価値があります。また、単体テストは、スタックトレースなどを超えてバグのトリアージに役立つこともわかります。アプリの個々の部分がどのように機能し、失敗するかを確認すると、全体として機能する、または全体として失敗する理由についての大きな洞察が得られます。


4

ほとんどの場合、そこにロジックがあればテストします。これには、特にプロパティに複数のものが設定されている場合、コンストラクタとプロパティが含まれます。

あまりにも多くのテストに関しては、議論の余地があります。すべての堅牢性をテストする必要があると言う人もいれば、効率的なテストを行うには、破損する可能性のあるもの(つまりロジック)だけをテストする必要があると言う人もいます。

私は個人的な経験だけから、2番目のキャンプに寄りかかると思いますが、誰かがすべてをテストしようと決心したとしても、それは多すぎるとは言えません...少しやりすぎかもしれませんが、多すぎません。

だから、いいえ-一般的な意味での「多すぎる」テストというものはなく、個人のためだけのものです。


3

テスト駆動開発とは、すべてのテストに合格したときにコーディングを停止することを意味します。

プロパティのテストがない場合、なぜそれを実装する必要があるのですか?「不正な」割り当ての場合に期待される動作をテストまたは定義しない場合、プロパティは何をすべきですか?

したがって、私はクラスが示すべきすべての動作を完全にテストするためにいます。「プリミティブ」プロパティを含みます。

このテストを簡単にするためTestFixtureに、値を設定/取得するための拡張ポイントを提供し、有効な値と無効な値のリストを取得し、プロパティが正しく機能するかどうかを確認する単一のテストを持つ単純なNUnitを作成しました。単一のプロパティをテストすると、次のようになります。

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

ラムダと属性を使用すると、これをさらにコンパクトに書くこともできます。私はMBUnitがそのようなものに対するいくつかのネイティブサポートさえ持っていることを集めます。しかし重要な点は、上記のコードはプロパティの意図を捉えているということです。

PS:おそらく、PropertyTestには、オブジェクトの他のプロパティが変更されていないことを確認する方法もあるはずです。うーん..描画ボードに戻ります。


mbUnitのプレゼンテーションに行きました。それは素晴らしいですね。
Johnno Nolan

しかし、デビッド、私にあなたに尋ねさせてください:あなたは上記のケントベックの反応に驚きましたか?彼の答えはあなたがあなたのアプローチを再考する必要があるかどうか疑問に思いますか?もちろん、誰もが「上からの回答」を持っているからではありません。しかし、ケントは最初にテストの中心的な支持者の一人と考えられています。あなたの考えにペニー!
チャーリーフラワー

@チャーリー:ケントの反応は非常に実用的です。私はさまざまなソースからのコードを統合するプロジェクトに「ただ」取り組んでおり、非常に高いレベルの信頼を提供したいと考えています。
デビッドシュミット

とは言え、私はテスト済みのコードよりも単純なテストを作成するよう努めており、このレベルの詳細は、すべてのジェネレーター、モジュール、ビジネスルール、およびバリデーターが統合される統合テストでのみ価値があるかもしれません。
デビッドシュミット

1

実行可能な最大のカバレッジに到達するように単体テストを行います。コードに到達できない場合は、カバレッジが可能な限りいっぱいになるまでリファクタリングします

筆記テストを盲目的に終えた後、私は通常各バグを再現する1つのテストケースを書きます

私はコードテストと統合テストを区別するのに慣れています。統合テスト中(これも単体テストですが、コンポーネントのグループなので、単体テストの目的は正確ではありません)、要件が正しく実装されるかどうかをテストします。


1

したがって、テストを作成してプログラミングを進めるほど、テストの細かさのレベルについて心配する必要がなくなります。振り返ってみると、行動を検証するという私の目標を達成するために、可能な限り簡単なことをしているようです。これは、自分のコードが私が求めていることを実行しているという確信の層を生み出していることを意味しますが、これは私のコードにバグがないことの絶対的な保証とは見なされません。正しいバランスは、標準的な動作をテストすることであり、1つまたは2つのエッジケースをテストしてから、デザインの次の部分に進むと思います。

これですべてのバグがカバーされるわけではなく、他の従来のテスト方法を使用してこれらをキャプチャすることを受け入れます。


0

一般的に、私は小さなものから始め、機能するはずの入力と出力を知っています。次に、バグを修正するときに、テストを追加して、修正した内容がテストされることを確認します。それはオーガニックで、私にはうまくいきます。

テストしすぎませんか?おそらく、ただし、アプリケーションのミッションクリティカル度次第ではありますが、一般的には注意を怠らないほうがいいでしょう。


0

ビジネスロジックの「コア」のすべてをテストする必要があると思います。Getter ans Setterも、受け入れたくない負の値またはnull値を受け入れる可能性があるためです。時間がある場合(常に上司に依存します)、他のビジネスロジックとこれらのオブジェクトを呼び出すすべてのコントローラーをテストすることをお勧めします(単体テストから統合テストにゆっくりと進みます)。


0

副作用のない単純なセッター/ゲッターメソッドの単体テストは行いません。しかし、私は他のすべてのパブリックメソッドを単体テストします。私はアルゴリズムのすべての境界条件のテストを作成して、ユニットテストの範囲を確認しようとしています。

大変な作業ですが、それだけの価値があると思います。デバッガーでコードをステップ実行するよりも、コード(テストコードでさえ)を記述したい。code-build-deploy-debugサイクルは非常に時間がかかり、ビルドに統合した単体テストをより徹底的に行うと、そのcode-build-deploy-debugサイクルに費やす時間が短縮されます。

なぜあなたがコーディングしているアーキテクチャも、あなたは言わなかった。ただし、Javaの場合は、Maven 2JUnitDbUnitCobertura、およびEasyMockを使用します。


言語にとらわれないかなりの質問であるとは言いませんでした。
ジョンノノーラン

TDDでのユニットテストは、コードを記述しているときに対象になるだけでなく、コードを継承し、ゲッター内で値をフォーマットすることが理にかなっていると考えられる人からも保護します。
Paxic 2009

0

それについて読むほど、いくつかの単体テストはいくつかのパターンに似ていると思います。不十分な言語の匂い。

簡単なゲッターが実際に正しい値を返すかどうかをテストする必要があるのは、ゲッター名とメンバー変数名を混在させることができるためです。rubyの 'attr_reader:name'を入力すると、これは発生しなくなります。Javaでは不可能です。

場合は、あなたのゲッターは、これまでの非自明な取得あなたはまだそれのためにテストを追加することができます。


ゲッターのテストは簡単なことだと私は同意します。ただし、コンストラクター内で設定するのを忘れるほど愚かかもしれません。したがって、テストが必要です。質問をして以来、考えが変わりました。私の答えを参照してくださいstackoverflow.com/questions/153234/how-deep-are-your-unit-tests/...
Johnnoノーラン

1
実際、ある意味で、単体テストは全体として言語の問題のにおいであると主張します。Eiffelのようなコントラクト(メソッドの事前/事後条件)をサポートする言語では、まだいくつかの単体テストが必要ですが、必要なものはそれほど多くありません。実際には、単純なコントラクトであっても、バグを簡単に見つけることができます。メソッドのコントラクトが壊れた場合、バグは通常、そのメソッドにあります。
Damien Pollet、

@Damien:たぶん、ユニットテストとコントラクトは、変装して本当に同じものですか?つまり、契約を「サポート」する言語は、基本的に、コードのスニペット(テスト)を簡単に記述できるようにするだけのものです。文法が十分に単純であれば、契約をネイティブでサポートしていない言語は、プリプロセッサーを作成することで、契約をサポートするように簡単に拡張できますよね?または、一方のアプローチ(契約または単体テスト)でできること、もう一方のアプローチではできないことはありますか?
j_random_hacker 2009

0

心配になるソースコードをテストしてください。

間違いを犯さない限り、非常に自信があるコードの部分をテストするのに役立ちません。

バグ修正をテストして、バグを修正するのが初めてで最後になるようにします。

あいまいなコード部分の信頼性をテストして、知識を作成します。

中程度のリファクタリングの前にテストして、既存の機能を壊さないようにします。


0

この回答は、特定のメソッドに使用する単体テストの数を、その重要度/重要性のために単体テストを実行したいことがわかっている場合に理解するためのものです。McCabeによるベーシスパステスト手法を使用すると、次のようにして、単純な「ステートメントカバレッジ」または「ブランチカバレッジ」よりもコードカバレッジの信頼性を定量的に向上させることができます。

  1. 単体テストするメソッドの循環的複雑度の値を決定します(たとえば、Visual Studio 2010 Ultimateは静的分析ツールを使用してこれを計算できます。それ以外の場合は、フローグラフメソッドを使用して手動で計算できます-http://users.csc 。 calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html
  2. メソッドを流れる独立したパスの基本セットをリストします。フローグラフの例については、上記のリンクを参照してください
  3. 手順2で決定された独立した基本パスごとに単体テストを準備する

すべての方法でこれを行うのですか?マジ?
クリストファー・ジョンソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.