web-dev-qa-db-ja.com

「オブジェクトではなく、インターフェイスに対するコード」と言うとき、プログラマは何を意味しますか?

私は学習するための非常に長くて骨の折れる探求を開始しました適用 TDDをワークフローに。私は、TDDがIoCの原則に非常によく適合するという印象を受けています。

ここSOでTDDタグ付きの質問のいくつかを参照した後、オブジェクトではなくインターフェイスに対してプログラムすることをお勧めします。

これが何であるか、そして実際のユースケースでそれをどのように適用するかの簡単なコード例を提供できますか?簡単な例は、私(および他の学習したい人)が概念を把握するための鍵です。

本当にありがとう。

78
delete

考慮してください:

_class MyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(MyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}
_

MyMethodMyClassのみを受け入れるため、単体テストのためにMyClassをモックオブジェクトに置き換えたい場合はできません。より良いのは、インターフェースを使用することです:

_interface IMyClass
{
    void Foo();
}

class MyClass : IMyClass
{
    //Implementation
    public void Foo() {}
}

class SomethingYouWantToTest
{
    public bool MyMethod(IMyClass c)
    {
        //Code you want to test
        c.Foo();
    }
}
_

特定の具体的な実装ではなく、インターフェイスのみを使用するため、MyMethodをテストできます。次に、そのインターフェイスを実装して、テスト目的で使用するあらゆる種類のモックまたはフェイクを作成できます。 Rhino MocksのRhino.Mocks.MockRepository.StrictMock<T>()のようなライブラリもあります。これらのライブラリは、任意のインターフェイスを取り、その場でモックオブジェクトを作成します。

82
Billy ONeal

それはすべて親密さの問題です。実装(実現されたオブジェクト)をコーディングする場合、その「他の」コードとの密接な関係にあります。つまり、どのように構築するか(つまり、コンストラクターのパラメーターとして、場合によってはセッターとして)、どのような依存関係があるのか​​、いつ破棄するのかを知る必要があり、おそらくそれなしではあまりできません。

実現されたオブジェクトの前のインターフェースを使用すると、いくつかのことができます-

  1. ファクトリーを活用してオブジェクトのインスタンスを構築できる/すべきです。 IOCコンテナはあなたのためにこれを非常にうまくやってくれます、またはあなたはあなた自身のものを作ることができます。実稼働環境では、実際のインスタンスを構築するか、クラスのモックインスタンスを作成できます。本番環境ではもちろんrealを使用しますが、テストでは、スタブまたは動的モックインスタンスを作成して、実行せずにさまざまなシステム状態をテストできます。システム。
  2. オブジェクトがどこにあるかを知る必要はありません。これは、対話したいオブジェクトがプロセスやシステムに対してローカルである場合とそうでない場合がある分散システムで役立ちます。 Java RMIまたは古いskool EJBをプログラミングしたことがある場合、クライアントが持っていないリモートネットワーキングとマーシャリングの役割を果たしたプロキシを隠している「インターフェイスと通信する」ルーチンを知っています。 WCFには、「インターフェイスと通信する」という同様の哲学があり、システムにターゲットオブジェクト/サービスとの通信方法を決定させます。

**更新** IOC Container(Factory))の例に対するリクエストがありました。ほとんどすべてのプラットフォームに多くの機能がありますが、コアでは次のように機能します。

  1. アプリケーションの起動ルーチンでコンテナを初期化します。一部のフレームワークは、構成ファイルまたはコード、あるいはその両方を介してこれを行います。

  2. コンテナが実装するインターフェースのファクトリとして作成する実装を「登録」します(例:ServiceインターフェースにMyServiceImplを登録します)。この登録プロセス中に、通常、新しいインスタンスが毎回作成されるか、シングル(ton)インスタンスが使用されるかなど、提供できるいくつかの動作ポリシーがあります。

  3. コンテナがオブジェクトを作成すると、作成プロセスの一部としてそれらのオブジェクトに依存関係が挿入されます(つまり、オブジェクトが別のインターフェイスに依存している場合、そのインターフェイスの実装が提供されます)。

疑似コードは次のようになります。

IocContainer container = new IocContainer();

//Register my impl for the Service Interface, with a Singleton policy
container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON);

//Use the container as a factory
Service myService = container.Resolve<Service>();

//Blissfully unaware of the implementation, call the service method.
myService.DoGoodWork();
18
hoserdude

インターフェイスに対してプログラミングする場合、具体的な型ではなく、インターフェイスのインスタンスを使用するコードを記述します。たとえば、コンストラクター注入を組み込む次のパターンを使用できます。コンストラクターインジェクションと制御の反転の他の部分は、インターフェイスに対してプログラムできる必要はありませんが、TDDとIoCの観点から来ているので、この方法で接続してコンテキストを提供します。に精通。

public class PersonService
{
    private readonly IPersonRepository repository;

    public PersonService(IPersonRepository repository)
    {
        this.repository = repository;
    }

    public IList<Person> PeopleOverEighteen
    {
        get
        {
            return (from e in repository.Entities where e.Age > 18 select e).ToList();
        }
    }
}

リポジトリオブジェクトは渡され、インターフェイスタイプです。インターフェイスを渡す利点は、使用方法を変更せずに具体的な実装を「スワップアウト」できることです。

たとえば、実行時に、IoCコンテナがデータベースにアクセスするように配線されたリポジトリを挿入すると仮定します。テスト時には、モックまたはスタブリポジトリを渡してPeopleOverEighteenメソッドを実行できます。

9

それはジェネリックだと思うことを意味します。具体的ではありません。

ユーザーにメッセージを送信することを通知するアプリケーションがあるとします。たとえば、インターフェイスiMessageを使用して作業している場合

interface iMessage
{
    public void Send();
}

ユーザーごとにメッセージの受信方法をカスタマイズできます。たとえば、誰かがメールで通知されるようにしたいので、IoCがEmailMessage具象クラスを作成します。他の人はSMSを必要とし、SMSMessageのインスタンスを作成します。

これらすべての場合において、ユーザーに通知するためのコードは変更されません。別の具象クラスを追加しても。

3
Lorenzo

ユニットテストを実行する際のインターフェイスに対するプログラミングの大きな利点は、個別にテストするか、テスト中にシミュレートする依存関係からコードの一部を分離できることです。

どこかで前にここで言及した例は、構成値にアクセスするためのインターフェースの使用です。 ConfigurationManagerを直接見るのではなく、構成値にアクセスできる1つ以上のインターフェイスを提供できます。通常、構成ファイルから読み取る実装を提供しますが、テストには、テスト値を返すか、例外などをスローするものを使用できます。

データアクセス層も考慮してください。特定のデータアクセス実装にビジネスロジックを緊密に結合すると、データベース全体で必要なデータを手に入れずにテストすることが難しくなります。データアクセスがインターフェイスの背後に隠れている場合、テストに必要なデータのみを提供できます。

インターフェイスを使用すると、テストに使用できる「表面積」が増加し、コードの個々のユニットを実際にテストする、よりきめの細かいテストが可能になります。

2
Andrew Kennan

ドキュメントを読んだ後、コードを使用する人のようにコードをテストします。コードを書いたり読んだりしているので、あなたが持っている知識に基づいて何かをテストしないでください。あなたのコードbehavesが期待通りであることを確認したい。

最良の場合は、テストを例として使用できるはずです。Pythonのdoctestsはこの良い例です。

これらのガイドラインに従えば、実装の変更は問題になりません。

また、私の経験では、アプリケーションの各「レイヤー」をテストすることをお勧めします。アトミックユニットがあり、それ自体は依存関係がなく、最終的にはそれ自体がユニットであるアプリケーションに到達するまで、他のユニットに依存するユニットがあります。

各層をテストする必要があります。ユニットAをテストすることにより、ユニットAが依存するユニットBもテストするという事実に依存しないでください(ルールも継承に適用されます)。これも実装の詳細として扱われるべきですあなたは自分自身を繰り返しているように感じるかもしれませんが。

一度書いたテストは変更される可能性は低いですが、テストするコードはほぼ間違いなく変更されることに注意してください。

実際にはIOおよび外の世界の問題もあるため、必要に応じてモックを作成できるようにインターフェイスを使用する必要があります。

より動的な言語では、これはそれほど問題ではありません。ここでは、ダックタイピング、多重継承、ミックスインを使用してテストケースを作成できます。一般的に継承を嫌い始めたら、おそらくそれを正しくしているでしょう。

2
DasIch

このスクリーンキャスト アジャイル開発とc#の実際のTDDについて説明しています。

インターフェイスに対してコーディングすることは、テストで実際のオブジェクトの代わりにモックオブジェクトを使用できることを意味します。優れたモックフレームワークを使用すると、モックオブジェクトで何でも好きなようにできます。

1
BЈовић