単体テストで何をテストする必要がありますか?


122

私は大学を卒業したばかりで、来週どこかで大学を始めています。単体テストを見てきましたが、あまり使用しませんでした。誰もがそれらについて話すので、私はいくつかのことをすべきだと思いました。

問題は、をテストすべきかわからないことです。一般的なケースをテストする必要がありますか?エッジケース?機能が適切にカバーされていることを知るにはどうすればよいですか?

特定のケースで機能が機能することをテストで証明することはできますが、機能が機能することを証明することはまったく役に立たないという恐ろしい気持ちが常にあります。


Roy Osheroveのブログをご覧ください。ビデオを含むユニットテストに関する多くの情報があります。彼はまた、「ユニットテストの芸術」という非常に良い本を書きました。
ピアズマイヤーズ

9
ほぼ5年後、あなたはそれについてどう思いますか?なぜなら、最近では「ユニットテストをしないこと」をもっとよく知っておくべきだと思うからです。行動駆動型開発は、あなたが尋ねたような質問から発展しました。
レミジウスパンケビチウス

回答:


121

これまでの私の個人的な哲学は次のとおりです。

  1. 可能なすべての一般的なケースをテストします。これは、何らかの変更を加えた後にそのコードが壊れたときにわかります(これは、自動化された単体テストの最大のメリットです)。
  2. おそらくエラーがあると思われるいくつかの非常に複雑なコードのエッジケースをテストします。
  3. バグを見つけたら、修正する前にそれをカバーするテストケースを書く
  4. 誰かが殺す時間があるときはいつでも、重要度の低いコードにエッジケーステストを追加します。

1
このおかげで、私はOPと同じ質問でこっちをいじっていました。
スティーブン

5
+1、ただし、ライブラリ/ユーティリティタイプの関数のエッジケースもテストして、論理APIがあることを確認します。たとえば、nullが渡されるとどうなりますか?空の入力はどうですか?これにより、設計が論理的になり、コーナーケースの動作を文書化できます。
ミケラ

7
#3は単体テストがどのように役立つかを示す実際の例であるため、非常に堅実な答えのようです。一度壊れると、再び壊れることがあります。
ライアングリフィス

始めたばかりの私は、テストを考案することに関してあまり創造的ではないことがわかりました。したがって、これらを上記の#3として使用します。これにより、これらのバグが再び検出されなくなることはありません。
ankush981

あなたの答えは、この人気の高いメディア記事hackernoon.com/…
BugHunterUK

67

これまでの多数の回答の中で、当面の質問に対する回答の重要な考慮事項である等価分割境界値分析に触れた人はいません。他の答えはすべて有用ですが、定性的ですが、ここで定量的であることが可能であり、望ましいです。@fishtoasterはいくつかの具体的なガイドラインを提供します。テストの定量化の裏で覗くだけですが、同値分割と境界値分析により、より良い結果が得られます。

同値分割、あなたが期待される成果に基づいてグループにすべての可能な入力のセットを分割します。1つのグループからの入力は同等の結果を生成するため、このようなグループは等価クラスと呼ばれます。(同等の結果は同一の結果を意味しないことに注意してください。)

簡単な例として、小文字のASCII文字を大文字に変換するプログラムを考えます。他のキャラクターは恒等変換を行う必要があります。つまり、変更しないでください。同等クラスへの可能な内訳の1つを次に示します。

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

最後の列は、すべてを列挙した場合のテストケースの数を報告します。技術的には、@ fishtoasterのルール1により、52のテストケースが含まれます。上記の最初の2行のテストケースはすべて「一般的なケース」に該当します。@fishtoasterのルール2は、上記の行3および4からも一部またはすべてを追加します。ただし、同値分割テストでは、各同値クラスのいずれかのテストケースで十分です。「a」または「g」または「w」を選択した場合、同じコードパスをテストしています。したがって、52個以上ではなく合計4個のテストケースがあります。

境界値分析では、わずかな改良を推奨しています。本質的には、同等クラスのすべてのメンバーが同等ではないことを示唆しています。つまり、境界の値も、それ自体がテストケースに値すると見なされる必要があります。(これを正当化する簡単な理由の1つは、悪名高いoff-by-oneエラーです!)したがって、各等価クラスに対して、3つのテスト入力を持つことができます。上記の入力ドメインを見て、ASCII値についてある程度の知識があれば、これらのテストケースの入力を思いつくかもしれません。

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(3つ以上の境界値を取得するとすぐに、元の等価クラスの描写を再検討することをお勧めしますが、これは単純であったため、改めて説明しませんでした。)したがって、境界値分析では、徹底的なテストを行うための128のテストケースと比較して、17のテストケース-完全なカバレッジの信頼性が高い。(言うまでもなく、組み合わせ論は、徹底的なテストは実際のアプリケーションでは単に実行不可能であると規定している!)


3
+1それはまさに私が直感的にテストを書く方法です。今、私はそれに名前を付けることができます:)それを共有してくれてありがとう。
guillaume31

+1「質的答えは有用であるが、それは可能である-と好ましい-定量的であること」
ジミーブレック-McKye

これは、指示が「どのようにテストで良いカバレッジを得ることができるか」である場合、これは良い答えだと思います。この上に実用的なアプローチを見つけることは有用だと思います-すべてのレイヤーのすべてのロジックのすべてのブランチをこの方法で徹底的にテストすることが目標ですか?
キーレンジョンストン

18

おそらく私の意見はあまり人気がありません。しかし、ユニットテストで経済的になることをお勧めします。ユニットテストが多すぎる場合、実際のコーディングではなくテストの維持に半分以上の時間を費やすことになります。

腸内の気分が悪いもの、または非常に重要かつ/または初歩的なもののテストを書くことをお勧めします。IMHO単体テストは、優れたエンジニアリングと防御的なコーディングに代わるものではありません。現在、私は多かれ少なかれ使用できないプロジェクトに取り組んでいます。本当に安定していますが、リファクタリングするのは苦痛です。実際、1年以内に誰もこのコードに触れたことがなく、そのベースとなっているソフトウェアスタックは4年前のものです。どうして?ユニットテストが散らかっているため、正確にはユニットテストと自動化された統合テストです。(キュウリなどを聞いたことはありますか?)そして、ここが最良の部分です:この(まだ)使い物にならないソフトウェアは、テスト駆動開発の先駆者である会社によって開発されました。:D

だから私の提案は:

  • 基本的なスケルトンを開発した後にテストの作成を開始します。そうしないと、リファクタリングが苦痛になる場合があります。他の人のために開発する開発者は、最初から要件を取得することはありません。

  • ユニットテストを迅速に実行できることを確認してください。統合テスト(キュウリなど)がある場合、少し時間がかかっても問題ありません。しかし、長期にわたるテストは面白くない、と私は信じています。(人々は、C ++の人気が低下した理由をすべて忘れています...)

  • このTDDスタッフはTDDエキスパートに任せてください。

  • はい、予期しない場所に応じて、エッジケースに集中することもあれば、一般的なケースに集中することもあります。ただし、常に予期しないことが予想される場合は、ワークフローと規律を再考する必要があります。;-)


2
テストがこのソフトウェアをリファクタリングするのに苦労する理由について詳しく説明してもらえますか?
マイクパートリッジ

6
ビッグ+1。代わりに、ルールの実装をテストするユニットテストの壁を持つことは、すべての変更はできるだけ多く2〜3倍を必要とします
TheLQ

9
不十分に作成された製品コードと同様に、不十分に作成された単体テストは保守が困難です。「ユニットテストが多すぎる」ということは、DRYを維持できないことに聞こえます。各テストは、システムの特定の部分に取り組む/証明する必要があります。
アラン

1
各ユニットテストは1つの項目をチェックする必要があるため、ユニットテストは多すぎず、テストが欠落しています。単体テストが複雑な場合、それは別の問題です。
落書き14

1
-1:この投稿の内容は不十分だと思います。言及されていることは複数あり、それらがすべてどのように関連しているかはわかりません。答えのポイントが「経済的」である場合、あなたの例はどのように関連していますか?あなたの例の状況(実際ですが)にはユニットテストが悪いようです。そのことからどのような教訓を学び、それが経済的に役立つかを説明してください。また、私は正直言って、あなたが言うときにあなたが何を意味するのか単に知らないLeave this TDD stuff to the TDD-experts
アレクサンダーバード

8

テスト駆動開発で最初にテストする場合、最初に失敗した単体テストを作成せずに機能を追加することはないため、カバレッジは90%以上の範囲になります。

事後にテストを追加する場合は、Michael Feathersによる「レガシーコードで効果的作業する」のコピーを入手して、コードにテストを追加する方法とコードをリファクタリングする方法の両方を検討することをお勧めしませんよりテストしやすくします。


そのカバレッジ率をどのように計算しますか?とにかくコードの90%をカバーすることはどういう意味ですか?
zneak

2
@zneak:それらを計算するコードカバレッジツールがあります。「コードカバレッジ」の簡単なグーグルでそれらの多くが表示されます。このツールは、テストの実行中に実行されるコードの行を追跡し、カバレッジパーセンテージを算出するためにアセンブリ内のコードの合計行を調整するベースを作成します。
スティーブンエバーズ

-1。質問に答えない:The problem is, I don't know _what_ to test
アレクサンダーバード

6

テスト駆動開発のプラクティスに従うことを開始すると、プロセスをガイドし、テスト対象を自然に理解できるようにガイドがソートします。開始する場所:

テストが最初に来る

テストを書く前にコードを決して書かないでください。説明については、Red-Green-Refactor-Repeatを参照してください。

回帰テストを書く

バグに遭遇したときはいつでも、テストケースを書き、失敗することを確認してください。失敗したテストケースを介してバグを再現できない限り、実際にはそれを発見できていません。

赤緑リファクタリング繰り返し

:実装しようとしている動作の最も基本的なテストを書くことから始めます。このステップは、作業中のクラスまたは関数を使用するサンプルコードを記述することと考えてください。コンパイル/構文エラーがなく、失敗することを確認してください。これは明らかなはずです。コードを書いていないので、失敗しなければなりませんよね?ここで学ぶべき重要なことは、テストが少なくとも1回失敗しない限り、パスした場合、何らかの偽りの理由で行ったことが原因でテストが成功するかどうかを確認できないことです。

Green:実際にテストに合格する最も単純で愚かなコードを記述します。賢くなろうとしないでください。あなたは明らかにエッジケースが、アカウントのテストテイクがあることを見ていても、していないそれを処理するコードを記述(:あなたはそれを後で必要になりますが、エッジケースを忘れないでください)。アイデアは、コードのすべての部分、すべてif、すべてtry: ... except: ...をテストケースによって正当化する必要があるということです。コードはエレガント、高速、または最適化する必要はありません。テストに合格するだけです。

リファクタリング:コードをクリーンアップし、メソッド名を正しく取得します。テストがまだ合格しているかどうかを確認します。最適化。テストを再度実行します。

繰り返します:テストでカバーできなかったエッジケースを覚えていますか?それで、今がその大きな瞬間です。その状況をカバーするテストケースを作成し、失敗を監視し、コードを作成し、合格し、リファクタリングします。

コードをテストする

特定のコードに取り組んでおり、これがまさにテストしたいものです。これは、ライブラリ関数、標準ライブラリ、またはコンパイラをテストしてはならないことを意味します。また、「世界」のテストを避けるようにしてください。これには、外部Web API、データベース集約的なものなどの呼び出しが含まれます。モックアップを試みることができるときはいつでも(同じインターフェースに従うが、静的な定義済みデータを返すオブジェクトを作成します)。


1
既に既存の(および、可能な限り)動作するコードベースがあると仮定して、私は何をしますか?
zneak

それは少し難しいかもしれません(コードの記述方法によって異なります)。回帰テストから始めて(常に意味があります)、ユニットテストを書いて、コードが何をしているのかを理解していることを証明できます。(一見)やらなければならない作業の量に圧倒されるのは簡単ですが、テストがない場合よりも常に優れているテストもあります。
リサードゾパ

3
-1 この質問に対する非常に良い答えだとは思わない。質問はTDDについてではなく、単体テストを書くときにをテストするを尋ねてます。実際の質問に対する良い答えは、TDD以外の方法論にも当てはまると思います。
ブライアンオークリー

1
触ったら、テストしてください。また、Clean Code(Robert C Martin)は、サードパーティコードの「学習テスト」を作成することを推奨しています。そうすれば、それを使用することを学び、新しいバージョンが使用している動作を変更する場合のテストがあります。
ロジャーウィルコックス

3

単体テストの場合は、設計されていることを実行することをテストすることから始めます。それはあなたが書く最初のケースであるべきです。デザインの一部が「迷惑メールを渡すと例外をスローする必要がある」場合、それもデザインの一部であるため、テストします。

それから始めます。最も基本的なテストの実行経験を積むと、それで十分かどうかを学び始め、テストが必要なコードの他の側面を確認し始めます。


0

標準的な答えは、「壊れる可能性のあるすべてをテストする」ことです。

簡単すぎて破れないのは何ですか?データフィールド、脳死プロパティアクセサー、および同様の定型的なオーバーヘッド。他の要素は、おそらく要件の特定可能な部分を実装し、テストされることでメリットが得られる可能性があります。

もちろん、走行距離と作業環境の慣行は異なる場合があります。


はい。では、どのケースをテストする必要がありますか?「通常の」ケースですか?エッジケース?
zneak

3
経験則?ゴールデンパスの真ん中、エッジの内側と外側の1つまたは2つ。
ジェフリーハンティン

@JeffreyHantinそれは別の答えの「境界値分析」です。
ロジャーウィルコックス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.