web-dev-qa-db-ja.com

単体テストは初めてですが、優れたテストの書き方は?

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

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

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

メソッドのテストを書いているとき、メソッド自体にすでに書いたものをもう一度書き直したい気分になります。
[。メソッドの最終的な動作が変更されなかった場合でも失敗します。

これは単なる感覚であり、前述したように、テストの経験はありません。経験豊富なテスターが、既存のアプリの優れたテストを作成する方法についてアドバイスをくれたら、それは大歓迎です。

編集:Stack Overflowに感謝します。15分もかからずに素晴らしい入力があり、ちょうど私がやったオンライン読書の時間の多くに答えました。

234
pixelastic

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

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

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

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

メソッドの内部を見て、何をしているのかを確認しないでください。したがって、内部を変更してもテストが失敗することはありません。プライベートメソッドが呼び出されていることを直接テストしないでください。プライベートコードがテストされているかどうかを確認したい場合は、コードカバレッジツールを使用してください。ただし、これに夢中にならないでください。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);
}

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

165
Mark Byers

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

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

それでも、テストは2回コーディングするようなものです。実際、アプリケーションコードよりもテストコード(量)が多いアプリケーションがありました。 1つの例は、非常に複雑な状態マシンです。さらにロジックを追加した後、すべてが以前のすべてのユースケースで常に機能することを確認する必要がありました。そして、これらのケースはコードを見るだけで追跡するのが非常に困難であったため、このマシン用の非常に優れたテストスイートを作成し、変更を加えても壊れないことを確信し、テストは数回私の尻を救いました。そして、ユーザーまたはテスターがフローまたはコーナーケースの原因不明のバグを見つけていたため、何がテストに追加され、二度と発生しませんでした。これにより、ユーザーは全体が非常に安定することに加えて、私の作業に自信を持ちました。そして、パフォーマンス上の理由で書き直さなければならなかった場合、何を推測してください。テストのおかげで、すべての入力で期待どおりに機能しました。

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

テストでは、関数fooが関数barを3回呼び出したことを文字通りテストするべきではありません。それは間違いです。結果と副作用が正しいかどうかを確認してください。内部のメカニズムではありません。

28
Dmitriy Likhten

単体テストを既存のコードに後付けすることは、そもそもテストでそのコードの作成を駆動するよりもfar難しいことは注目に値します。これは、レガシーアプリケーションを扱う際の大きな質問の1つです...ユニットテストの方法は?これは以前から何度も尋ねられてきたので(だからはだまされやすい質問として閉じられるかもしれません)、人々は通常ここにいます:

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

受け入れられた回答の本の推奨を2番目に挙げましたが、それ以上に、回答にリンクされている情報がさらにあります。

18
David

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

テストを小さくしてください:要件ごとに1つのテスト。

後で、変更を加える(または新しいコードを書く)必要がある場合は、最初に1つのテストを書いてみてください。一つだけです。次に、テスト駆動開発の第一歩を踏み出します。

14
Jon Reid

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

13
fresskoma

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

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

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

7
Justin Niessner

テストは保守性を改善することになっています。メソッドを変更し、テストが失敗した場合、canは良いことです。一方、メソッドをブラックボックスと見なす場合、メソッド内に何があってもかまいません。事実、いくつかのテストでは物事をモックする必要があり、そのような場合、メソッドをブラックボックスとして扱うことはできません。できることは、統合テストを書くことだけです。テスト対象のサービスの完全にインスタンス化されたインスタンスをロードし、アプリで実行するような動作をさせます。その後、ブラックボックスとして扱うことができます。

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.

これは、コードを書いた後にテストを書いているからです。反対にそれを行った場合(最初にテストを書いた場合)、このように感じることはありません。

3
hvgotcodes