何が良い単体テストになるのですか?[閉まっている]


97

多くの人が自動テストをたくさん書いていると思いますし、単体テストの際によくある落とし穴にも遭遇したことでしょう。

私の質問は、将来の問題を回避するために、テストを作成するための行動規則に従っていますか?より具体的に言うと、優れた単体テストの特性は何ですか?またはどのようにテストを記述しますか?

言語にとらわれない提案が奨励されます。

回答:


93

まず、ソースのプラグインから始めましょう-JUnitを使用したJavaでの実用的なユニットテスト(C#-Nunitを使用したバージョンもありますが、これも持っています。大部分は不可知論です。推奨されます。)

Good TestsはA TRIPである必要があります(頭字語は十分にベタベタしていません。この本のチートシートのプリントアウトがあり、これが正しいことを確認するために抜かなければなりませんでした。)

  • 自動:テストの呼び出しとPASS / FAILの結果のチェックを自動化する必要があります
  • 完全:カバレッジ; バグはコード内の特定の領域に集中する傾向がありますが、必ずすべての主要なパスとシナリオをテストしてください。テストされていない領域を知る必要がある場合は、ツールを使用してください
  • 繰り返し可能:テストは毎回同じ結果を生成する必要があります。テストでは、制御できないパラメータに依存しないでください。
  • 独立:非常に重要です。
    • テストでは、一度に1つだけをテストする必要があります。複数のアサーションは、すべてが1つの機能/動作をテストしている限り問題ありません。テストが失敗した場合、問題の場所を正確に特定する必要があります。
    • テストは互いに依存すべきではありません -分離されています。テスト実行の順序についての仮定はありません。セットアップ/ティアダウンを適切に使用して、各テストの前に「白紙の状態」を確認してください
  • プロフェッショナル:長期的には、本番と同じかそれ以上のテストコードが存在するため、テストコードについては、優れた設計と同じ標準に従ってください。意図を明らかにする名前を持つよく因数分解されたメソッドクラス、重複なし、適切な名前のテストなど

  • 優れたテストはFastも実行します。実行に0.5秒以上かかるテストは、作業する必要があります。テストスイートの実行にかかる時間が長いほど、実行頻度が低くなります。開発者が実行間でこっそりしようとする変更が多いほど..何かが壊れた場合..どの変更が原因であるかを特定するのに時間がかかります。

2010-08更新:

  • 読み取り可能:これはProfessionalの一部と見なすことができますが、十分に強調することはできません。酸テストは、チームのメンバーではない誰かを見つけて、数分以内にテスト中の動作を理解するように依頼することです。テストは本番コードと同じように維持する必要があります。そのため、より多くの労力を費やしても読みやすくなります。テストは対称的(パターンに従う)で簡潔でなければなりません(一度に1つの動作をテストします)。一貫した命名規則を使用してください(例:TestDoxスタイル)。「偶然の詳細」でテストを雑然としないようにします。ミニマリストになります。

これらとは別に、その他のほとんどは、低利益の作業を削減するためのガイドラインです。たとえば、「所有していないコードをテストしないでください」(サードパーティのDLLなど)。ゲッターとセッターのテストに取り掛からないでください。費用対効果の比率や欠陥の可能性に注意してください。


Mocksの使用に反対するかもしれませんが、これはユニットテストのベストプラクティスの非常に優れた記事でした。
ジャスティンスタンダード

「A TRIP」という頭字語が便利だと思うので、これを答えとして上げます。
スポーク2008

3
大部分は同意しますが、所有していないコードをテストすることには利点があることを指摘したいと思います...あなたの要件を満たしていることをテストしています。他にどのようにして、アップグレードがシステムを破壊しないと確信できますか?(しかし、そうするときはもちろん、コスト/メリットの比率を念頭に置いてください。)
幻滅

@Craig-あなたは(インターフェースレベルの)回帰テスト(または場合によっては学習者テスト)を参照していると思います。サードパーティコードの「単体」テストは記述しません。ベンダーがそのコードについて私よりも知っているb。ベンダーは特定の実装を保持する義務はありません。私はそのコードベースへの変更を制御せず、アップグレードで壊れたテストを修正することに時間を費やしたくありません。ですから、私が使用する動作の高レベルの回帰テストをいくつかコード化したい(そして、壊れたときに通知を受けたい)
Gishu

@岐阜:はい、絶対に!テストはインターフェイスレベルでのみ実行する必要があります。実際、実際に使用する機能をテストする必要があります。さらに、これらのテストを何を書くかを選択するとき、単純でわかりやすい「ユニット」テストフレームワークは、通常、この要件に完全に適合しています。
幻滅

42
  1. 膨大なテストを書かないでください。「単体テスト」の「ユニット」が示唆するように、それぞれを可能な限りアトミック分離したものにします。必要な場合は、典型的なユーザー環境の多くを手動で再作成するのではなく、モックオブジェクトを使用して前提条件を作成します。
  2. 明らかに機能するものをテストしないでください。サードパーティベンダーのクラス、特にコーディングするフレームワークのコアAPIを提供するクラスのテストは避けてください。たとえば、ベンダーのHashtableクラスへのアイテムの追加をテストしないでください。
  3. NCoverなどのコードカバレッジツールを使用して、まだテストしていないエッジケースを見つけることを検討してください
  4. 実装にテストを書いてみてください。テストは、実装が準拠する仕様のようなものと考えてください。Cf. テスト駆動開発のより具体的なブランチである、動作駆動開発も。
  5. 一貫している。一部のコードのテストのみを記述する場合、それはほとんど役に立ちません。チームで作業していて、他の一部またはすべてがテストを作成していない場合も、あまり役に立ちません。自分自身と他のすべての人に、テストの重要性(および時間節約の特性)を納得させるか、または気にしないでください。

1
いい答えだ。しかし、配信のすべてを単体テストしないのであれば、それほど悪くはありません。確かにそれは望ましいことですが、バランスと実用主義が必要です。再:同僚を参加させる; 場合によっては、価値を示すため、および参照ポイントとしてそれを行う必要があるだけです。
マーティンクラーク

1
同意する。ただし、長期的には、そこにあるテストに依存できる必要があります。つまり、一般的な落とし穴がテストによって検出されると想定できる必要があります。そうでなければ、利点は大幅に減少します。
ソレンKuklau

2
「一部のコードのテストを作成するだけでは、ほとんど役に立ちません。」これは本当ですか?私は20%のコードカバレッジ(致命的/失敗する可能性のある領域)のプロジェクトを持っていますが、それらは私を大いに助けてくれました。プロジェクトも同様に順調です。
dr。悪

1
私はスラウに同意する。テストの数が少ない場合でも、十分に記述され、十分に分離されていれば、非常に役立ちます。
2009年

41

ここでの答えのほとんどは、実際にテスト自体(方法)を作成するのではなく、ユニットテストのベストプラクティス全般(いつ、どこで、なぜ、何を)に対処しているようです。「どうやって」という部分についてはかなり具体的な質問だったので、会社で行った「ブラウンバッグ」のプレゼンテーションから引用したものだと思いました。

Wompのテスト作成の5つの法則:


1.長くわかりやすいテストメソッド名を使用します。

   - Map_DefaultConstructorShouldCreateEmptyGisMap()
   - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
   - Dog_Object_Should_Eat_Homework_Object_When_Hungry()

2. Arrange / Act / Assertスタイルでテストを記述します

  • この組織戦略はしばらく前から存在し、多くのことを呼んでいましたが、「AAA」という頭字語の導入は、これを広めるための優れた方法です。すべてのテストをAAAスタイルと一致させることで、テストの読み取りと保守が容易になります。

3.常にアサートで失敗メッセージを提供します。

Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element 
processing events was raised by the XElementSerializer");
  • ランナーアプリケーションで何が失敗したかを明らかにする、シンプルでありながらやりがいのあるプラクティス。メッセージを提供しないと、通常、失敗の出力に「予期された真、偽だった」などのメッセージが表示されます。そのため、実際にテストを読んで何が悪いのかを調べる必要があります。

4.テストの理由をコメントします。ビジネスの前提は何ですか。

  /// A layer cannot be constructed with a null gisLayer, as every function 
  /// in the Layer class assumes that a valid gisLayer is present.
  [Test]
  public void ShouldNotAllowConstructionWithANullGisLayer()
  {
  }
  • これは当たり前のように思えるかもしれませんが、この方法は、そもそもテストの背後にある理由を理解していない人々からテストの整合性を保護します。テストが検証しているという仮定を人が理解しなかったという理由だけで、完全に問題のない多くのテストが削除または変更されるのを見てきました。
  • テストが簡単な場合、またはメソッド名が十分に説明的なものである場合は、コメントを省略しても問題ありません。

5.すべてのテストは、それが接触するリソースの状態を常に元に戻す必要があります

  • 実際のリソースを扱わないように、可能な場合はモックを使用してください。
  • クリーンアップはテストレベルで行う必要があります。テストは、実行順序に依存してはなりません。

2
ポイント1、2、および5のため、+ 1が重要です。3と4は、すでに説明的なテストメソッド名を使用している場合、単体テストではかなり過剰に見えますが、スコープが大きい(機能テストまたは受け入れテスト)場合は、テストの文書化をお勧めします。
スポーク2009年

実用的な知識と実例のための+1
Phil

17

これらの目標を念頭に置いてください(MeszarosによるxUnit Test Patternsの本から転載)

  • テストはリスクを軽減するべきであり、導入するべきではありません。
  • テストは簡単に実行できるはずです。
  • システムはテストを中心に進化するので、テストは維持しやすいはずです

これを簡単にするいくつかのこと:

  • テストは、1つの理由でのみ失敗するはずです。
  • テストは1つだけをテストする必要があります
  • テストの依存関係を最小限に抑える(データベース、ファイル、UIなどに依存しない)

xUnitフレームワークを使用して統合テストを実行することもできますが、統合テストとユニットテストを分離しておくことを忘れないでください。


Gerard Meszaros著の「xUnit Test Patterns」から改作したことを意味していると思います。xunitpatterns.com
スポーク

うん、あなたは正しい。私はそれをポストでクリアします
メンデルト2008

優れた点。ユニットテストは非常に便利ですが、システムを変更しようとする試みに巨額の負担をかける複雑で相互依存のユニットテストの罠に陥らないようにすることが非常に重要です。
ウェッジ

9

テストは分離する必要があります。あるテストが別のテストに依存するべきではありません。さらに、テストは外部システムに依存すべきではありません。つまり、コードが依存するコードではなく、コードをテストします。これらの相互作用は、統合テストまたは機能テストの一部としてテストできます。


9

優れた単体テストのいくつかの特性:

  • テストが失敗した場合、どこに問題があるのか​​すぐにわかるはずです。デバッガーを使用して問題を追跡する必要がある場合、テストの粒度が十分ではありません。テストごとに1つのアサーションを持つことは、ここで役立ちます。

  • リファクタリングすると、テストが失敗することはありません。

  • テストは非常に速く実行する必要があるため、実行をためらうことはありません。

  • すべてのテストは常に合格する必要があります。非決定的な結果はありません。

  • 単体テストは、実稼働コードと同じように、よく因数分解する必要があります。

@Alotor:ライブラリがその外部APIでの単体テストのみを持つべきだと提案しているなら、私は同意しません。外部の呼び出し元に公開しないクラスを含む、各クラスの単体テストが必要です。(ただし、プライベートメソッドのテストを作成する必要があると感じた場合は、リファクタリングする必要があります。


編集:「テストごとに1つのアサーション」によって引き起こされる重複についてのコメントがありました。具体的には、シナリオをセットアップするコードがあり、それについて複数のアサーションを作成したいが、テストごとに1つのアサーションしかない場合、複数のテスト間でセットアップを複製する可能性があります。

私はそのアプローチをとりません。代わりに、シナリオごとにテストフィクスチャを使用します。大まかな例は次のとおりです。

[TestFixture]
public class StackTests
{
    [TestFixture]
    public class EmptyTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
        }

        [TestMethod]
        [ExpectedException (typeof(Exception))]
        public void PopFails()
        {
            _stack.Pop();
        }

        [TestMethod]
        public void IsEmpty()
        {
            Assert(_stack.IsEmpty());
        }
    }

    [TestFixture]
    public class PushedOneTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
            _stack.Push(7);
        }

        // Tests for one item on the stack...
    }
}

テストごとに1つのアサーションのみについて意見が異なります。テストのアサーションが多いほど、カットアンドペーストのテストケースが少なくなります。テストケースはシナリオまたはコードパスに焦点を当てる必要があり、アサーションはそのシナリオを満たすためのすべての前提条件と要件から派生する必要があると思います。
ルーカスB

DRYが単体テストに適用されることに同意すると思います。私が言ったように、「ユニットテストはよく因数分解されるべきです」。ただし、重複を解決するには複数の方法があります。1つは、あなたが言っているように、最初にテスト対象のコードを呼び出し、次に複数回アサートする単体テストを持つことです。別の方法は、シナリオの新しい「テストフィクスチャ」を作成することです。これにより、初期化/セットアップのステップ中にテスト対象のコードが呼び出され、単純にアサートされる一連の単体テストが作成されます。
Jay Bazuzi

私の経験則では、コピーと貼り付けを使用している場合は、何か問題があります。私のお気に入りの言葉の1つは、「コピーと貼り付けはデザインパターンではない」です。ユニットテストごとに1つのアサーションを使用することも一般的には良い考えだと私は同意しますが、常に主張するわけではありません。より一般的な「単体テストごとに1つのテスト」が好きです。ただし、通常はユニットテストごとに1つのアサートに変換されます。
ジョンターナー

7

あなたが求めているのは、テスト中のクラスの振る舞いの描写です。

  1. 予想される動作の検証。
  2. エラーケースの検証。
  3. クラス内のすべてのコードパスのカバレッジ。
  4. クラス内のすべてのメンバー関数を実行します。

基本的な目的は、クラスの行動に対する自信を高めることです。

これは、コードのリファクタリングを調べるときに特に役立ちます。Martin Fowlerが彼のWebサイトでテストに関する興味深い記事を公​​開しています。

HTH。

乾杯、

ロブ


ロブ-メカニカルこれは良いですが、意図を逃しています。なぜあなたはこれをすべてやったのですか?このように考えることは、TDDの道筋をたどる他者を助けるかもしれません。
Mark Levison、2011

7

テストは本来失敗するはずです。次に、それらをパスさせるコードを書く必要があります。そうしないと、バグがあり常にパスするテストを書くリスクがあります。


@Rismoそれ自体は排他的ではありません。Quarrelsomeがここで書いたものは、定義上、TDDの一部である「テストファースト」方法論に限定されます。TDDでは、リファクタリングも考慮されます。私が読んだ最も「賢いパンツ」の定義は、TDD = Test First + Refactorです。
スポーク2008

ええ、TDDである必要はありません。テストが最初に失敗することを確認してください。その後、残りの部分を配線します。これは、TDDを実行するときに最も一般的に発生しますが、TDDを使用しない場合にも適用できます。
平凡な2008

6

前述のPragmatic Unit Testingの本の正しいBICEPの頭字語が好きです。

  • 正しい:結果は正しいですか?
  • B:全てですB oundary条件を修正?
  • I:私たちは確認することができ、私は関係をnverse?
  • C:私たちができますcのロスチェック結果、他の手段を使用していますか?
  • E:私たちは強制することができ、電子の発生するrror条件を?
  • P:ですのp境界内erformance特性?

個人的には、正しい結果(1 + 1は加算関数で2を返す必要があります)を確認し、考えられるすべての境界条件(2つの数値の合計は、add関数の整数の最大値より大きい)であり、ネットワーク障害などのエラー条件を強制します。


6

優れたテストは保守可能である必要があります。

複雑な環境でこれを行う方法はまだわかりません。

コードベースが数百から数百または数百万のコード行に到達し始めると、すべての教科書がばらばらになり始めます。

  • チームの相互作用が爆発する
  • テストケースの数が爆発する
  • コンポーネント間の相互作用が爆発します。
  • すべてのユニットテストをビルドする時間は、ビルド時間の重要な部分になります
  • APIの変更は、何百ものテストケースに波及する可能性があります。製品コードの変更は簡単でしたが。
  • プロセスを正しい状態に順序付けるために必要なイベントの数が増えると、テストの実行時間が長くなります。

優れたアーキテクチャは、相互作用の爆発の一部を制御できますが、必然的に、システムがより複雑になるにつれて、自動テストシステムはそれに伴って成長します。

ここから、トレードオフに対処する必要があります。

  • 外部APIのみをテストします。それ以外の場合は内部をリファクタリングすると、テストケースが大幅にやり直されます。
  • カプセル化されたサブシステムがより多くの状態を保持するため、各テストのセットアップとティアダウンはより複雑になります。
  • 毎晩のコンパイルと自動化されたテストの実行は、数時間に増大します。
  • コンパイル時間と実行時間の増加は、設計者がすべてのテストを実行しない、または実行しないことを意味します
  • テストの実行時間を減らすために、セットアップとティアダウンを減らすためにテストのシーケンスを検討する

また、次のことも決定する必要があります。

コードベースのどこにテストケースを保存しますか?

  • テストケースをどのように文書化しますか?
  • テストフィクスチャを再利用してテストケースのメンテナンスを節約できますか?
  • 毎晩のテストケースの実行が失敗するとどうなりますか?トリアージは誰ですか?
  • モックオブジェクトをどのように維持しますか?20個のモジュールがすべて独自のモックロギングAPIを使用している場合、APIの変更はすぐに波及します。テストケースだけでなく、20個のモックオブジェクトも変化します。これらの20のモジュールは、多くの異なるチームによって数年にわたって作成されました。その典型的な再利用問題です。
  • 個人とそのチームは、自動化されたテストの価値を理解しています。彼らは、他のチームがそれを行っている方法が気に入らないだけです。:-)

私は永遠に続けることができますが、私のポイントは次のとおりです。

テストは保守可能である必要があります。


5

これらの原則については、このMSDNマガジンの記事で少し前から取り上げました。

「良い」ユニットテストを定義する方法は、次の3つのプロパティを持っているかどうかです。

  • それらは読み取り可能です(命名、アサート、変数、長さ、複雑さなど)。
  • それらは保守可能です(ロジックなし、過剰指定なし、状態ベース、リファクタリングなど)。
  • それらは信頼できる(統合テストではなく、適切なものをテストし、分離する。)

ロイ、私は心から同意します。これらは、エッジケースのカバレッジよりもはるかに重要です。
Matt Hinze、

信頼できる-優れた点!
ratkok 2011年

4
  • ユニットテストはユニットの外部APIをテストするだけであり、内部動作をテストするべきではありません。
  • TestCaseの各テストでは、このAPI内の1つの(そして1つだけの)メソッドをテストする必要があります。
    • 失敗した場合は、追加のテストケースを含める必要があります。
  • テストのカバレッジをテストします。ユニットがテストされると、このユニット内の行の100%が実行されているはずです。

2

ジェイ・フィールズはユニットテストを書くことについて多くの良いアドバイスをしていて、彼が最も重要なアドバイスを要約する投稿があります。そこであなたはあなたがあなたの文脈について批判的に考えるべきであり、そのアドバイスがあなたにとって価値があるかどうかを判断すべきだと読むでしょう。あなたはここで驚くべき答えをたくさん得ますが、あなたのコンテキストに最適なものを決めるのはあなた次第です。それらを試して、それがあなたに悪臭がする場合はリファクタリングしてください。

敬具


1

ささいな2行の方法がうまくいくと思い込まないでください。クイックユニットテストを作成することは、nullテストの欠落、マイナス記号の配置ミス、および/または微妙なスコープエラーによる噛み込みを防ぐ唯一の方法です。


1

私は「A TRIP」という答えを2番目に挙げていますが、テストは互いに依存するべきです。

どうして?

DRY-あなた自身を繰り返さないでください-テストにも適用されます!テストの依存関係は、1)セットアップ時間の節約、2)フィクスチャリソースの節約、3)障害の特定に役立ちます。もちろん、テストフレームワークがファーストクラスの依存関係をサポートしている場合のみです。そうでなければ、私は認めます、彼らは悪いです。

フォローアップhttp://www.iam.unibe.ch/~scg/Research/JExample/


私はあなたに同意します。TestNGは、依存関係を簡単に許可できる別のフレームワークです。
Davide

0

多くの場合、単体テストはモックオブジェクトまたはモックデータに基づいています。私は3種類の単体テストを書くのが好きです:

  • 「一時的な」ユニットテスト:独自のモックオブジェクト/データを作成し、それを使用して機能をテストしますが、すべてを破棄し、トレースを残しません(テストデータベースにデータがないなど)。
  • 「永続的」ユニットテスト:コード内の関数をテストして、後で独自のユニットテストのためにより高度な関数が必要とするオブジェクト/データを作成します(これらの高度な関数が独自のモックオブジェクト/データのセットを作成するたびに再作成することは避けます)
  • 「永続ベース」の単体テスト:永続的な単体テストによってすでに存在する(別の単体テストセッションで作成されたため)モックオブジェクト/データを使用した単体テスト。

ポイントは、すべての機能をテストできるようにするために、すべてを再生しないようにすることです。

  • すべてのモックオブジェクト/データがすでに存在するため、私は非常に頻繁に3番目の種類を実行します。
  • モデルが変わるたびに、第2種を実行します。
  • 最初の関数を実行して、非常に基本的な関数を時々チェックし、基本的な回帰をチェックします。

0

2種類のテストについて考え、それらを異なる方法で扱います-機能テストとパフォーマンステスト。

それぞれに異なる入力と指標を使用します。テストのタイプごとに異なるソフトウェアを使用する必要がある場合があります。


それでは単体テストはどうですか?
2008

0

Roy Osheroveのユニットテストの命名基準で説明されている一貫したテスト命名規則を使用しています。特定のテストケースクラスの各メソッドには、次の命名スタイルMethodUnderTest_Scenario_ExpectedResultがあります。

    最初のテスト名セクションは、テスト中のシステム内のメソッドの名前です。
    次は、テストされている特定のシナリオです。
    最後にそのシナリオの結果です。

各セクションはアッパーキャメルケースを使用し、アンダースコアで区切られています。

テストを実行するときに、テストがテスト対象のメソッドの名前でグループ化されていると便利です。また、他の開発者がテストの意図を理解できるようにする規則があります。

テスト対象のメソッドがオーバーロードされている場合は、メソッド名にパラメーターを追加します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.