2

私は何度もAPIの設計/実装に携わっており、このジレンマに直面しています。

私は情報隠蔽を非常に強力に支持しており、内部クラス、プライベートメソッド、パッケージプライベート修飾子などを含むがこれらに限定されないさまざまな手法を使用しようとしています。

これらの手法の問題は、優れたテスト容易性を妨げる傾向があることです。そして、これらのテクニックのいくつかは解決できますが(たとえば、クラスを同じパッケージに入れることによるパッケージのプライベート性)、他のテクニックはそれほど簡単に取り組むことができず、反射魔法または他のトリックが必要です。

具体的な例を見てみましょう。

public class Foo {
   SomeType attr1;
   SomeType attr2;
   SomeType attr3;

   public void someMethod() {
      // calculate x, y and z
      SomethingThatExpectsMyInterface something = ...;
      something.submit(new InnerFoo(x, y, z));
   }

   private class InnerFoo implements MyInterface {
      private final SomeType arg1;
      private final SomeType arg2;
      private final SomeType arg3;

      InnerFoo(SomeType arg1, SomeType arg2, SomeType arg3) {
         this.arg1 = arg1;
         this.arg2 = arg2;
         this.arg3 = arg3;
      }

      @Override
      private void methodOfMyInterface() {
         //has access to attr1, attr2, attr3, arg1, arg2, arg3
      }
   }
}

公開しないことには大きな理由がありますInnerFoo。他のクラスはありません。ライブラリは公開契約を定義しておらず、作成者が意図的にアクセスできるようにしたくないため、ライブラリにアクセスする必要があります。ただし、100%TDDコーシャであり、リフレクショントリックなしでアクセスできるようにするには、InnerFoo次のようにリファクタリングする必要があります。

private class OuterFoo implements MyInterface {
   private final SomeType arg1;
   private final SomeType arg2;
   private final SomeType arg3;
   private final SomeType attr1;
   private final SomeType attr2;
   private final SomeType attr3;

   OuterFoo(SomeType arg1, SomeType arg2, SomeType arg3, SomeType attr1, SomeType attr2, SomeType attr3) {
      this.arg1 = arg1;
      this.arg2 = arg2;
      this.arg3 = arg3;
      this.attr1 = attr1;
      this.attr2 = attr2;
      this.attr3 = attr3;
   }

   @Override
   private void methodOfMyInterface() {
      //can be unit tested without reflection magic
   }
}

この例には3つの属性しか含まれていませんが、5〜6を使用するのはかなり合理的であり、OuterFooコンストラクターは8〜10のパラメーターを受け入れる必要があります。ゲッターを上に追加すると、すでに100行の完全に役に立たないコードがあります(テスト用にこれらの属性を取得するには、ゲッターも必要になります)。はい、ビルダーパターンを提供することで状況を少し改善することができますが、これは過剰なエンジニアリングであるだけでなく、TDD自体の目的を損なうものだと思います。

この問題の別の解決策は、クラスの保護されたメソッドを公開し、Fooそれを拡張してFooTest、必要なデータを取得することです。protected繰り返しになりますが、メソッドはコントラクトを定義し、それを公開することで暗黙的に署名したため、これも悪いアプローチだと思います。

誤解しないでください。テスト可能なコードを書くのが好きです簡潔でクリーンなAPI、短いコードブロック、読みやすさなどが好きです。しかし、単体テストが簡単であるという理由だけで情報隠蔽に関して犠牲を払うのは好きではありません。

誰かがこれについて(一般的に、そして特に)何か考えを提供できますか?与えられた例のための他のより良い解決策はありますか?

4

6 に答える 6

4

この種のことに対する私の頼りになる答えは「テストプロキシ」です。テストパッケージで、保護されたデータへの「パススルー」アクセサーを含むサブクラスをテスト対象システムから派生させます。

利点:

  • 公開したくないメソッドを直接テストまたはモックすることができます。
  • テストプロキシはテストパッケージに含まれているため、本番コードで使用されないようにすることができます。
  • テストプロキシをテスト可能にするために必要なコードの変更は、クラスを直接テストする場合よりもはるかに少なくなります。

短所:

  • クラスは継承可能である必要があります(no final
  • アクセスする必要のある非表示のメンバーを非公開にすることはできません。保護はあなたができる最善のことです。
  • これは厳密にはTDDではありません。TDDは、そもそもテストプロキシを必要としないパターンに適しています。
  • あるレベルでは、プロキシと実際のSUTの間の「統合」に依存しているため、これは厳密には単体テストではありません。

要するに、これは通常珍しいはずです。私はこれをUI要素にのみ使用する傾向があります。ベストプラクティス(および多くのIDEのデフォルトの動作)は、ネストされたUIコントロールをクラスの外部からアクセスできないものとして宣言することです。間違いなく良いアイデアなので、呼び出し元がUIからデータを取得する方法を制御できますが、そのロジックをテストするための既知の値をコントロールに与えることも困難になります。

于 2011-02-17T17:04:57.443 に答える
3

リフレクションの使い方を考え直すべきだと思います。

独自の欠点がありますが、ダミーコードなしで必要なセキュリティモデルを維持できるのであれば、それは良いことかもしれません。多くの場合、反射は必要ありませんが、適切な代替手段がない場合もあります。

情報隠蔽への別のアプローチは、クラス/オブジェクトをブラックボックスとして扱い、非公開メソッドにアクセスしないことです(ただし、これにより、「間違った」理由、つまり答えは正しいが間違った理由でテストに合格することができます)。

于 2011-02-17T17:27:05.907 に答える
2

要約すると、情報隠蔽がどのようにテスト容易性を低下させているのかわかりません。

SomethingThatExpectsMyInterface直接構築するのではなく、このメソッドで使用されるものを注入する場合:

public void someMethod() {
   // calculate x, y and z
   SomethingThatExpectsMyInterface something = ...;
   something.submit(new InnerFoo(x, y, z));
}

次に、単体テストで、このクラスにのモックバージョンを挿入し、さまざまな入力でSomethingThatExpectsMyInterface呼び出したときに何が起こるかを簡単に主張できます。つまり、は特定の値の引数を受け取ります。someMethod()mockSomething

InnerFooはSomethingThatExpectsMyInterface、そのタイプの引数を受け取った場合、プライベートクラスになることはできないため、とにかくこの例を単純化しすぎた可能性があります。

「情報隠蔽」は、クラス間で渡すオブジェクトが秘密である必要があることを必ずしも意味しません。このクラスの詳細InnerFooやその他の詳細を認識するために、このクラスを使用する外部コードを必要としないということです。他の人と通信します。

于 2011-02-17T17:05:34.200 に答える
2

SomethingThatExpectsMyInterface外でテストできますFooよね?submit()を実装する独自のテストクラスを使用してそのメソッドを呼び出すことができますMyInterface。そのユニットが世話をします。今、あなたはFoo.someMethod()そのよくテストされたユニットとあなたのテストされていない内部クラスでテストしています。それは理想的ではありませんが、それほど悪くはありません。テストドライブsomeMethod()すると、内部クラスが暗黙的にテストドライブされます。いくつかの厳格な基準からすると、これは純粋なTDDではないことはわかっていますが、それで十分だと思います。失敗したテストを満たす場合を除いて、内部クラスの行を記述していません。テストとテストされたコードの間に単一レベルの間接参照があることは、大きな問題にはなりません。

于 2011-02-17T17:33:12.117 に答える
0

あなたの例では、Fooクラスには本当に協力者のInnerFoo が必要なようです。

私の意見では、情報隠蔽とテスト容易性の間の緊張は、「その部分の合計よりも単純な複合」というモットーによって解決されます。

Fooは、コンポジット上のファサードです(この場合は、InnerFooだけですが、重要ではありません)。ファサードオブジェクトは、意図された動作でテストする必要があります。InnerFooオブジェクトコードがFooの動作のテストによって十分に駆動されていないと感じる場合は、InnerFooがデザインで何を表しているかを検討する必要があります。デザインコンセプトを見逃しているのかもしれません。それを見つけ、名前を付け、その責任を定義したら、その動作を個別にテストできます。

于 2011-03-06T10:23:52.827 に答える
-1

私が嫌いなのは、情報隠蔽に関して犠牲を払うことです

まず、Pythonを数年間使用します。

private特に役に立ちません。クラスの拡張が難しくなり、テストが難しくなります。

「隠す」という立場を再考することを検討してください。

于 2011-02-17T20:25:00.910 に答える