テストされたクラスをスパイするのは悪い習慣ですか?


12

私はクラスの内部呼び出しが通常であるプロジェクトに取り組んでいますが、結果は何回も単純な値です。例(実際のコードではありません):

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

このタイプのコードの単体テストを書くのは、実際の条件の実装ではなく、条件システムをテストするだけなので、非常に困難です。(私は別のテストでそれを行います。)実際、条件を実装する関数を渡した方が良いでしょうし、テストではモックを提供するだけです。このアプローチの問題は騒々しいです:ジェネリックを多く使用します

実用的なソリューション。ただし、テスト対象のオブジェクトをスパイにし、内部関数の呼び出しを模擬することです。

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

ここでの懸念は、SUTの実装が効果的に変更されることであり、テストを実装と同期させておくことが問題になる場合があります。これは本当ですか?この内部メソッド呼び出しの混乱を避けるためのベストプラクティスはありますか?

アルゴリズムの一部について説明していることに注意してください。そのため、いくつかのクラスに分割することは望ましい決定ではない場合があります。

回答:


14

単体テストでは、テストするクラスをブラックボックスとして扱う必要があります。重要なのは、そのパブリックメソッドが期待どおりに動作することだけです。クラスが内部状態とプライベートメソッドを介してこれを達成する方法は重要ではありません。

この方法で意味のあるテストを作成することは不可能だと思うとき、それはあなたのクラスが強力すぎて、やりすぎだというサインです。これらの機能の一部を、個別にテストできる個別のクラスに移動することを検討する必要があります。


1
私はずっと前にユニットテストのアイデアを理解し、それらの多くを成功裏に書いてきました。紙の上では何かが単純に見え、コードではもっとひどく見えることを欺いているだけで、最終的には非常にシンプルなインターフェースを備えているものの、入力の世界の半分をモックする必要があります。
allprog

@allprog多くのモックを行う必要がある場合、クラス間の依存関係が多すぎるようです。それらの間の結合を減らしようとしましたか?
フィリップ

@allprogがそのような状況にある場合、クラス設計のせいです。
-itsbruce

頭痛の種になるのはデータモデルです。ORMルールおよび他の多くの要件を遵守する必要があります。純粋なビジネスロジックとステートレスコードを使用すると、単体テストをより簡単に正しく実行できます。
allprog

3
単体テストでは、必ずしもSUTをバックボックスとして処理する必要はありません。これがユニットテストと呼ばれる理由です。依存関係をモックすることで、環境に影響を与えることができ、モックする必要があるものを知るために、内部の一部も知る必要があります。ただし、これはもちろん、SUTを何らかの方法で変更する必要があるという意味ではありません。ただし、スパイはいくつかの変更を行うことができます。
allprog

4

findError()およびcheckFirstCondition()などがクラスのパブリックメソッドである場合、findError()事実上、同じAPIからすでに利用可能な機能のファサードになります。これには何の問題もありませんが、既存のテストと非常によく似たテストを作成する必要があるということです。この複製は、単にパブリックインターフェイスの複製を反映しています。それは、この方法を他の方法と異なる方法で扱う理由ではありません。


内部メソッドは、テスト可能にする必要があり、SUTをサブクラス化したり、ユニットテストを静的な内部クラスとしてSUTクラスに含めたりしたくないため、公開されています。しかし、私はあなたのポイントを得る。しかし、このような状況を回避するための適切なガイドラインは見つかりませんでした。チュートリアルは常に、実際のソフトウェアとは関係のない基本レベルにとどまりました。それ以外の場合、スパイの理由は、テストコードの重複を避け、テストのスコープをスコープにすることです。
allprog

3
適切な単体テストのためにヘルパーメソッドを公開する必要があることに同意しません。メソッドのコントラクトがさまざまな条件をチェックすることを規定している場合、同じパブリックメソッド(「サブコントラクト」ごとに1つ)に対して複数のテストを作成しても問題はありません。単体テストのポイントは、1対1のメソッドとテストの対応によってパブリックメソッドの表面的なカバレッジを達成することではなく、すべてのコードのカバレッジを達成することです。
キリアンフォス

テストにパブリックAPIのみを使用することは、内部のピースを1つずつテストするよりもはるかに複雑です。私は議論しません、このアプローチは最善ではなく、私の質問が示す後知恵があると思います。最大の問題は、関数がJavaで構成できないことであり、回避策は非常に簡潔です。しかし、実際の単体テストのための他のソリューションはないようです。
allprog

3

単体テストは契約をテストする必要があります。彼らにとって唯一の重要なことです。契約の一部ではないものをテストすることは時間の無駄だけでなく、潜在的なエラーの原因です。開発者が実装の詳細を変更するときにテストを変更するのを見ると、アラームベルが鳴るはずです。その開発者は(意図的かどうかにかかわらず)彼の間違いを隠しているかもしれません。 実装の詳細を故意にテストすると、この悪い習慣が強制され、エラーがマスクされる可能性が高くなります。

内部呼び出しは実装の詳細であり、パフォーマンスの測定にのみ関心があります。これは通常、単体テストの仕事ではありません。


いいね。しかし、実際には、入力してコードと呼ばなければならない「文字列」は、関数についてほとんど知識のない言語にあります。理論的には、問題を簡単に記述し、あちこちで置き換えて問題を単純化できます。コードでは、この柔軟性を実現するために、多くの構文上のノイズを追加する必要があります。amethod bに同じクラスのmethodの呼び出しが含まれる場合、のテストにaはのテストを含める必要がありますb。そして、パラメータとしてb渡されない限り、これを変更する方法はありませんaが、他の解決策はありません。
allprog

1
bがパブリックインターフェイスの一部である場合は、とにかくテストする必要があります。そうでない場合は、テストする必要はありません。あなたがそれをテストしたいという理由だけで公開した場合、あなたは間違っていました。
-itsbruce

@Philipの回答に対する私のコメントをご覧ください。まだ言及していませんが、データモデルが悪の原因です。純粋でステートレスなコードは簡単です。
allprog

2

まず、あなたが書いた関数の例についてテストするのは難しいのだろうか?私が見る限り、さまざまな入力を渡して、正しいブール値が返されることを確認することができます。私は何が欠けていますか?

スパイに関しては、スパイとモックを使用するいわゆる「ホワイトボックス」テストは、書くべきテストコードが非常に多いだけでなく、実装が行われるたびに、書くのがはるかに多くの作業です変更した場合は、テストも変更する必要があります(インターフェイスが同じ場合でも)。また、この種のテストは、すべての追加テストコードが正しいことを確認する必要があるため、ブラックボックステストよりも信頼性が低く、ブラックボックスユニットテストがインターフェイスと一致しない場合に失敗することを信頼できます、テストではモックのみを使用して、実際のコードをあまりテストしないこともあるため、モックの過剰使用については信頼できません モックが正しくない場合、テストは成功する可能性が高いですが、コードはまだ壊れています。

ホワイトボックステストの経験がある人なら誰でも、書いて維持するのが大変だと言うことができます。信頼性が低いという事実と相まって、ホワイトボックステストはほとんどの場合、はるかに劣っています。


注意していただきありがとうございます。サンプル関数は、複雑なアルゴリズムで記述する必要があるものよりも数桁単純です。実際には、質問は次のようになります:いくつかの部分でスパイを使用してアルゴリズムをテストするのは問題ですか?これはステートフルコードではなく、すべての状態が入力引数に分離されます。問題は、サブ関数に適切なパラメーターを提供せずに、例の複雑な関数をテストしたいという事実です。
allprog 14年

Java 8での関数型プログラミングの夜明けにより、これはわずかにエレガントになりましたが、異なる(単独では役に立たない)部分を「use once」に抽出するよりも、アルゴリズムの場合、単一のクラスで機能を維持する方が良い選択ですテスト可能性のためだけにクラス。この点で、スパイはモックと同じことをしますが、視覚的に一貫したコードを爆破する必要はありません。実際には、モックと同じセットアップコードが使用されます。私は極端な場所から離れたいので、特定の場所ではあらゆる種類のテストが適切かもしれません。どうにかしてテストすることは、どうにかするよりもずっと良いです。:)
allprog 14年

「複雑な機能をテストしたい。サブ機能に正しいパラメーターを提供する必要はない」-そこにあなたが意味するものが得られない。どのサブ機能ですか?「複雑な機能」で使用されている内部機能について話していますか?
BT 14年

私の場合、これがスパイ行為に役立つのです。内部機能は制御が非常に複雑です。コードのためではなく、論理的に複雑なものを実装しているためです。別のクラスにあるものを移動するのは自然なオプションですが、これらの機能だけではまったく役に立ちません。したがって、クラスをまとめてスパイ機能で制御する方が良いオプションであることが判明しました。ほぼ1年間問題なく動作し、モデルの変更に簡単に耐えることができました。それ以来、このパターンを使用したことはありませんが、場合によっては実行可能であると言って良いと思います。
allprog 14年

@allprog "論理的に複雑"-その複雑な場合、複雑なテストが必要です。それを回避する方法はありません。スパイはあなたのためにそれをより難しく、より複雑にします。スパイを使用して別の関数内の特殊な動作をテストするのではなく、独自にテストできる理解可能なサブ関数を作成する必要があります。
BT
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.