単体テストは初めてですが、優れたテストを作成するにはどうすればよいですか?[閉まっている]


267

私はユニットテストの世界にはかなり慣れていません。今週、既存のアプリにテストカバレッジを追加することにしました。

これは、主にテストするクラスの数のためだけでなく、テストの作成がすべて私にとって初めてのことであるため、大きなタスクです。

たくさんのクラスのテストをすでに書いていますが、今は正しくやっているかどうか疑問に思っています。

メソッドのテストを書いているとき、メソッド自体に既に書いたものをもう一度書き直しているような気がします。
私のテストはメソッドに緊密にバインドされているようです(すべてのコードパスをテストし、いくつかの内部メソッドが特定の引数で何度も呼び出されることを期待しています)。そのため、メソッドをリファクタリングすると、たとえメソッドの最終的な動作は変更されていません。

これは単なる感じであり、前述のとおり、私はテストの経験がありません。経験豊富なテスターが、既存のアプリの優れたテストを作成する方法についてアドバイスをいただければ幸いです。

編集:私はスタックオーバーフローに感謝したいと思います。15分未満ですばらしい入力があり、オンラインで読んだ時間よりも多くの回答を得ました。


1
これは単体テストに最適な本です:manning.com/osherove単体テストのすべてのベストプラクティス、推奨事項、禁止事項について説明しています。
エルビB

これらすべての答えが除外していることの1つは、単体テストはドキュメントのようなものであることです。エルゴは、関数を作成する場合、その入力と出力(および場合によっては副作用)を説明することで、その意図を文書化します。単体テストは、これを検証するためのものです。そして、後であなた(または他の誰か)がコードに変更を加える場合、ドキュメントはどのような変更を加えることができるかの境界を説明する必要があり、ユニットテストは境界が維持されることを確認します。
Thomas Tempelmann

回答:


187

私のテストはメソッドに緊密にバインドされているようです(すべてのコードパスをテストし、いくつかの内部メソッドが特定の引数で何度も呼び出されることを期待しています)。そのため、メソッドをリファクタリングすると、テストが失敗したように見えます。メソッドの最終的な動作は変わりませんでした。

あなたはそれを間違っていると思います。

単体テストは次のことを行う必要があります。

  • 1つの方法をテストする
  • そのメソッドにいくつかの特定の引数を提供する
  • 結果が期待どおりであることをテストする

メソッドの内部を調べて何が行われているかを確認することはできません。したがって、内部を変更してもテストが失敗することはありません。プライベートメソッドが呼び出されていることを直接テストしないでください。プライベートコードがテストされているかどうかを確認したい場合は、コードカバレッジツールを使用してください。しかし、これに夢中にならないでください。100%のカバー率は必須ではありません。

メソッドが他のクラスのパブリックメソッドを呼び出し、これらの呼び出しがインターフェイスによって保証されている場合、モックフレームワークを使用してこれらの呼び出しが行われていることをテストできます。

期待される結果を動的に生成するために、メソッド自体(またはメソッドが使用する内部コード)を使用しないでください。期待される結果は、テストケースにハードコード化して、実装が変更されても変更されないようにする必要があります。単体テストで行うべきことの簡単な例を次に示します。

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;
    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

結果の計算方法はチェックされないことに注意してください。結果が正しいことだけが確認されます。できるだけ多くのシナリオをカバーするまで、上記のような単純なテストケースをどんどん追加していきます。コードカバレッジツールを使用して、興味深いパスを逃していないかどうかを確認します。


13
どうもありがとう、あなたの答えはより完全でした。私はモックオブジェクトが本当に何であるかをよりよく理解しました:他のメソッドへのすべての呼び出しをアサートする必要はありません、関連するものだけです。私は、物事がどのように行われるかを知る必要はありませんが、正しく行われることを知っています。
pixelastic

2
私はあなたがそれを間違っていると思います。ユニットテストは、コード実行フロー(ホワイトボックステスト)に関するものです。ブラックボックステスト(提案内容)は、通常、機能テスト(システムおよび統合テスト)で使用される手法です。
ウェス

1
「単体テストは1つの方法をテストするべきだ」私は実際には同意しません。単体テストでは、1つの論理的な概念をテストする必要があります。それは、多くの場合、必ずしもそうではありません1つのメソッドとして表現されている間
robertmain

35

単体テストでは、テスト駆動(最初にテスト、コード2番目)とコード1番目、テスト2番目の両方が非常に役立つことがわかりました。

コードを書く代わりに、テストを書く。コードを書いてから、コードが何をすべきかを考えます。それのすべての使用目的について考えてから、それぞれのテストを作成してください。テストの作成はコーディング自体よりも高速ですが、より複雑であることがわかりました。テストは意図をテストする必要があります。また、テスト作成フェーズでコーナーケースを見つける意図を考えます。そしてもちろん、テストを作成しているときに、いくつかの使用法の1つがバグの原因であることがわかる場合があります(私がよく見つけるものであり、このバグがデータを破損せず、チェックされなかったことを非常に嬉しく思います)。

しかし、テストは2回コーディングするようなものです。実際、私はアプリケーションコードよりもテストコード(量)が多いアプリケーションを使用していました。1つの例は、非常に複雑な状態機械です。さらにロジックを追加した後は、以前のすべてのユースケースですべてが常に機能することを確認する必要がありました。そして、それらのケースはコードを見るのが非常に難しいため、このマシン用の優れたテストスイートを用意して、変更を加えても壊れないことを確信し、テストによってお尻が数回保存されました。そして、ユーザーまたはテスターが、フローまたはコーナーケースが考慮されていないバグを見つけていたので、何がテストに追加され、二度と起こらなかったと思います。これにより、全体が非常に安定していることに加えて、ユーザーは私の仕事に本当に自信を持っています。そして、パフォーマンス上の理由でそれを書き直す必要があったとき、何を推測しますか、

のような単純な例function square(number)はすべてすばらしいですし、多くの時間をテストに費やすのはおそらく悪い候補です。重要なビジネスロジックを実行するもの、つまりテストが重要な場所。要件をテストします。配管をテストするだけではありません。要件が変更された場合は、何を推測するか、テストも変更する必要があります。

テストは、関数fooが関数barを3回呼び出したことを文字通りテストするものであってはなりません。それは間違いです。内部の力学ではなく、結果と副作用が正しいかどうかを確認します。


2
いい答えです。コードの後に​​テストを書くことはまだ有用であり、可能であると確信しました。
pixelastic

2
最近の完璧な例。とてもシンプルな機能でした。trueを渡すと、1つの処理を実行し、falseを実行すると別の処理を実行します。とてもシンプル。関数が意図したとおりに機能することを確認するための4つのテストのチェックが必要でした。私は行動を少し変えます。テストを実行し、問題をPOWします。おもしろいのは、アプリケーションを使用するときに問題が発生せず、複雑な場合にのみ発生することです。テストケースがそれを見つけ、私は頭痛の時間を節約しました。
Dmitriy Likhten、2010

「テストは意図をテストするべきである。」これは要約すると思います。つまり、コードの意図された使用法を検討し、コードがそれらに対応できることを確認する必要があります。また、テストが実際にテストする必要のある範囲と、コードを変更するとき、その変更がコードの規定されたすべての使用にどのように影響するかをすぐには考えられないという考えを示します–テスト意図されたすべてのユースケースを満たさない変更を防ぎます。
Greenstick

18

単体テストを既存のコードに組み込むことは、そもそもテストでそのコードの作成を推進するよりもはるかに困難であることは注目に値します。これは、レガシーアプリケーションを扱う上での大きな問題の1つです...単体テストの方法は?これは以前に何度も尋ねられており(したがって、あなた誤った質問として締め切られる可能性あります)、人々は通常、次のようになります。

既存のコードをテスト駆動開発に移動する

私は承認された回答の本の推奨事項を2番目に取り上げますが、それ以外にも、回答にリンクされたより多くの情報があります。


3
最初または2番目にテストを作成する場合、どちらも問題ありませんが、テストを作成するときは、テストを作成できるようにコードがテスト可能であることを確認してください。あなたは「どうやってこれをテストできるか」ということを考えて、それ自体がより良いコードを書く原因になることがよくあります。テストケースの改造は常に大したことではありません。とても厳しい。それは時間の問題ではなく、量とテスト容易性の問題です。私は今上司に近づくことができず、1000以上のテーブルと使用のテストケースを書きたいと言います。今では多すぎるため、1年かかり、ロジック/決定の一部が忘れられます。あまり長く延期しないでください:P
Dmitriy Likhten

2
おそらく受け入れられた答えは変わった。Linxから、Roy Osheroveによるユニットテストのアートmanning.com/osherove
thelem

15

コードを完全に網羅するためのテストを記述しないでください。要件を保証するテストを記述します。不要なコードパスを発見するかもしれません。逆に、必要な場合は、何らかの要件を満たすために存在します。それが何であるかを見つけて、(パスではなく)要件をテストします。

テストを小さく保つ:要件ごとに1つのテスト。

後で、変更(または新しいコードを作成)する必要がある場合は、最初に1つのテストを作成してみてください。一つだけです。次に、テスト駆動開発の最初のステップを実行します。


おかげで、小さな要件に対して小さなテストを1つずつ行うことは理にかなっています。学んだ教訓。
pixelastic

13

単体テストは、関数/メソッド/アプリケーションから得られる出力に関するものです。結果がどのように生成されるかはまったく問題ではなく、正しいことだけが問題です。したがって、内部メソッドの呼び出しなどをカウントするアプローチは間違っています。私が行う傾向があるのは、座って、特定の入力値または特定の環境が与えられた場合にメソッドが返すものを記述し、次に、返された実際の値を思いついたものと比較するテストを記述します。


よろしくお願いします!私は自分が間違っていると感じていましたが、実際に誰かに話してもらう方がいいです。
pixelastic

8

テストするメソッドを記述する前に、ユニットテストを記述してください。

それは間違いなくあなたが物事がどのように行われているかについて少し違った考えをすることを強制するでしょう。メソッドがどのように機能するかは分からないでしょう。

メソッドがこれらの結果を取得する方法ではなく、常にメソッドの結果をテストする必要があります。


はい、できます。メソッドが既に記述されている場合を除きます。私はそれらをテストしたいだけです。将来、メソッドの前にテストを書くつもりです。
pixelastic

2
@pixelasticはメソッドが作成されていないふりをしていますか?
committedandroider

4

テストは保守性を向上させることになっています。あなたは方法やテスト休憩変更した場合にできる良いことを。一方、メソッドをブラックボックスとして見る場合は、メソッド内の内容は問題になりません。実際には、一部のテストでは物事をモックする必要があり、そのような場合、メソッドをブラックボックスとして実際に処理することはできません。実行できる唯一のことは、統合テストを作成することです。テスト対象のサービスの完全にインスタンス化されたインスタンスをロードし、アプリで実行するのと同じように動作させます。その後、それをブラックボックスとして扱うことができます。

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

これは、コードを記述した後でテストを記述しているためです。逆にそれを行った場合(最初にテストを記述した場合)は、このように感じられません。


ブラックボックスの例をお寄せいただきありがとうございます。そのように考えていません。以前に単体テストを発見したかったのですが、残念ながらそうではなく、テストを追加するレガシーアプリで立ち往生しています。壊れた感じなしに既存のプロジェクトにテストを追加する方法はありませんか?
pixelastic

1
後にテストを書くことは、前にテストを書くこととは異なります。ただし、実行できることは、最初に失敗するようにテストを設定し、次にクラスをテスト下に置くことでテストをパスするようにします。モックについても同じです。最初はモックに期待はありませんが、テスト対象のメソッドがモックで何かを実行してテストに合格するため、失敗します。この方法で多くのバグを見つけても私は驚かないでしょう。
hvgotcodes 2010

また、あなたの期待に本当に具体的です。テストがオブジェクトを返すことだけを主張せず、オブジェクトにさまざまな値があることをテストします。値がnullと想定されている場合、それがnullであることをテストします。また、いくつかのテストを追加した後で、意図したリファクタリングを行うことで、少し分解することもできます。
hvgotcodes 2010
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.