0

テスト可能なコードの書き方について調べていて、依存性注入のデザイン パターンに出くわしました。

この設計パターンは非常に理解しやすく、実際には何もありません。オブジェクト自体が値を作成するのではなく、値を要求します。

ただし、現在取り組んでいるアプリケーションでこれをどのように使用できるかを考えていると、複雑な問題があることに気付きました。次の例を想像してください。

public class A{

   public string getValue(){
      return "abc";
   }
}


public class B{
   private A a;

   public B(A a){
      this.a=a;
   }
   public void someMethod(){ 
      String str = a.getValue();
   }
}

someMethod ()A のモックを作成し、必要getValue()なものを返すことができるため、単体テストは簡単になります。

クラス B の A への依存関係は、コンストラクターを介して注入されますが、これは、A をクラス B の外部でインスタンス化する必要があるため、この依存関係は代わりに別のクラスに移動したことを意味します。これは何層にもわたって繰り返され、ある時点でインスタンス化を行う必要があります。

ここで質問ですが、依存関係の挿入を使用する場合、これらすべてのレイヤーを介して依存関係を渡し続けるというのは本当ですか? コードが読みにくくなり、デバッグに時間がかかるようになりませんか? そして、「最上位」レイヤーに到達したら、そのクラスをどのように単体テストしますか?

4

3 に答える 3

4

あなたの質問を正しく理解していることを願っています。

依存関係の注入

いいえ、依存関係をすべてのレイヤーに渡すわけではありません。それらと直接対話するレイヤーにのみそれらを渡します。例えば:

public class PaymentHandler {

    private customerRepository;

    public PaymentHandler(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public void handlePayment(CustomerId customerId, Money amount) {
        Customer customer = customerRepository.findById(customerId);
        customer.charge(amount);
    }
}

public interface CustomerRepository {
    public Customer findById(CustomerId customerId);
}

public class DefaultCustomerRepository implements CustomerRepository {

    private Database database;    

    public CustomerRepository(Database database) {
        this.database = database;
    }

    public Customer findById(CustomerId customerId) {
        Result result = database.executeQuery(...);
        // do some logic here
        return customer;
    }
}

public interface Database {
    public Result executeQuery(Query query);
}

PaymentHandlerは のことを認識せずDatabase、 とのみ会話しCustomerRepositoryます。の注入はDatabaseリポジトリ層で停止します。

コードの可読性

フレームワークやライブラリを使用せずに手動でインジェクションを行うと、多くのボイラープレート コードを含む Factory クラスになってしまう可能性がreturn new D(new C(new B(), new A());あります。この問題を解決するために、Guiceのような DI フレームワークを使用して、非常に多くのファクトリを作成しないようにする傾向があります。

ただし、実際に作業/ビジネス ロジックを実行するクラスの場合は、直接の共同作業者とのみ会話し、必要な作業を行うため、より読みやすく理解しやすいものにする必要があります。

単体テスト

「トップ」レイヤーとは、PaymentHandlerクラスを意味すると思います。この例では、スタブ クラスを作成し、それに対してチェックできるオブジェクトをCustomerRepository返してから、スタブを に渡して、正しい金額が請求されているかどうかをチェックできます。CustomerPaymentHandler

一般的な考え方は、テスト対象のクラス (この例ではクラス) の動作を安全にアサートできるように、出力を制御するために偽のコラボレーターを渡すことPaymentHandlerです。

インターフェイスを使用する理由

上記のコメントで述べたように、具体的なクラスではなくインターフェイスに依存する方が望ましいです。インターフェイスは、より優れたテスト容易性 (モック/スタブが容易) と簡単なデバッグを提供します。

お役に立てれば。

于 2013-04-08T18:12:14.890 に答える
1

IMO、あなたの質問は、パターンを理解していることを示しています。

正しく使用すると、すべての依存関係が解決されて注入されるコンポジション ルートが作成されます。ここで IoC コンテナーを使用すると、依存関係が解決され、レイヤーを介してそれらが渡されます。

これは、多くの人がアンチパターンと見なしている Service Location パターンとは正反対です。

コンポジション ルートを使用しても、明確で関連性のある依存関係を持つ適切に設計されたクラスは、合理的に自己文書化する必要があるため、コードの読み取り/理解が低下することはありません。構成ルートの単体テストについてはよくわかりません。それは目立たない役割を持っているので、テスト可能でなければなりません。

于 2013-04-09T08:23:29.490 に答える