内部でテストせずにDIを使用するクラスのユニットテスト


12

1つのメインクラスと2つの小さなクラスにリファクタリングされたクラスがあります。メインクラスはデータベースを使用し(多くのクラスと同様)、メールを送信します。メインクラスが持つようにIPersonRepositoryし、IEmailRepository彼の順番に2つのより小さいクラスに送信する注入しました。

ここで、メインクラスの単体テストを行い、クラスの内部動作を単体テストしないことを学びました。ユニットテストを壊すことなく内部動作を変更できるはずだからです。

クラスが使用するようしかし、IPersonRepositoryIEmailRepository、私はHAVEのためのいくつかの方法について(モック/ダミー)の結果を指定しますIPersonRepository。メインクラスは、既存のデータに基づいていくつかのデータを計算し、それを返します。それをテストしたい場合、IPersonRepository.GetSavingsByCustomerId戻り値x を指定せずにテストを作成する方法がわかりません。しかし、それから私のユニットテストは、どのメソッドをモックするか、そしてどのメソッドをモックしないかを「知っている」ので、内部動作を「知っている」。

テストは内部を知らずに、依存関係を注入したクラスをどのようにテストできますか?

バックグラウンド:

私の経験では、このような多くのテストはリポジトリのモックを作成し、モックに適切なデータを提供するか、実行中に特定のメソッドが呼び出されたかどうかをテストします。いずれにせよ、テストは内部について知っています。

今、私はテストが実装について知るべきではないという理論についてのプレゼンテーションを見ました(以前聞いたことがあります)。第一に、それがどのように機能するかをテストていないためです。また、実装を変更すると、実装を「知っている」ためすべての単体テストが失敗します。私はテストが実装に気付かないという概念が好きですが、それを達成する方法がわかりません。


1
メインクラスがIPersonRepositoryオブジェクトを予期するとすぐに、そのインターフェイスとそれが記述するすべてのメソッドは「内部」ではなくなるため、実際にはテストの問題ではありません。本当の質問は、「クラスを公開することなくクラスを小さな単位にリファクタリングする方法」です。答えは「これらのインターフェースを無駄にしない」です(例えば、インターフェースの分離の原則に従うことによって)。@DavidArnoの答えの私見ポイント2です(別の答えでそれを繰り返す必要はないと思います)。
ドックブラウン

回答:


7

メインクラスは、既存のデータに基づいていくつかのデータを計算し、それを返します。それをテストしたい場合、IPersonRepository.GetSavingsByCustomerId戻り値x を指定せずにテストを作成する方法がわかりません。しかし、それから私のユニットテストは、どのメソッドをモックするか、そしてどのメソッドをモックしないかを「知っている」ので、内部動作を「知っている」。

これは「内部をテストしない」という原則に違反するものであり、人々が見落としがちなものであるというのは正しいことです。

テストは内部を知らずに、依存関係を注入したクラスをどのようにテストできますか?

この違反を回避するために採用できる2つのソリューションがあります。

1)の完全なモックを提供しIPersonRepositoryます。現在説明しているアプローチは、呼び出すメソッドをモックするだけで、テスト対象のメソッドの内部動作にモックを結合することです。のすべてのメソッドにモックを提供IPersonRepositoryする場合、そのカップリングを削除します。内部の動作はモックに影響を与えずに変更できるため、テストの脆弱性が軽減されます。

このアプローチには、DIメカニズムをシンプルに保つという利点がありますが、インターフェイスで多くのメソッドを定義している場合は、多くの作業を作成することができます。

2)IPersonRepository注入、GetSavingsByCustomerIdメソッドの注入、節約値の注入を行わないでください。インターフェース実装全体をインジェクトする際の問題は、「尋ねる、言わない」システムをインジェクト(「伝える、聞かない」)し、2つのアプローチを混ぜ合わせることです。「純粋なDI」アプローチを採用する場合、メソッドは、オブジェクトを与えられるのではなく、節約値が必要な場合に呼び出す正確なメソッドを提供(通知)する必要があります(オブジェクトはメソッドを呼び出すように要求する必要があります)。

このアプローチの利点は、モック(テスト対象のメソッドに注入するテストメソッド以外)が不要になることです。欠点は、メソッドの変更の要件に応じてメソッドシグネチャが変更される可能性があることです。

どちらのアプローチにも長所と短所があるため、ニーズに最も適したものを選択してください。


1
私はアイデア2が本当に好きです。なぜ私はそれを前に考えなかったのか分かりません。IRepository全体に依存関係を作成した理由を疑うことなく、かなり長い間IRepositoriesに依存関係を作成してきました(多くの場合、非常に多くのメソッドシグネチャが含まれます)。 2つの方法。したがって、IRepositoryを注入する代わりに、必要な(のみ)メソッドシグネチャを注入または渡すことができます。
ミシェル

1

私のアプローチは、必要なデータを含む単純なファイルから読み取るリポジトリの「モック」バージョンを作成することです。

これは、個々のテストがモックのセットアップを「知らない」ことを意味しますが、明らかにテストプロジェクト全体はモックを参照し、セットアップファイルなどを持ちます。

これにより、フレームワークのモック作成に必要な複雑なモックオブジェクトのセットアップが回避され、UIテストなどのためにアプリケーションの実際のインスタンスで「モック」オブジェクトを使用できます。

「モック」は完全に実装されているため、テストシナリオ用に特別にセットアップするのではなく、たとえば実装を変更すると、GetSavingsForCustomerが顧客も削除するようになります。テストを壊さないでください(もちろん、実際にテストを壊さない限り)、単一のモック実装を更新するだけで、セットアップを変更せずにすべてのテストが実行されます


2
これは、テストされたクラスが動作しているメソッドの正確なデータでモックを作成するという状況につながります。そのため、実装が変更されるとテストが中断するという問題がまだあります。
ミシェル

1
それでも私はあなたが意味を考えるシナリオのための完璧ではないが、私のアプローチは、優れている理由を説明するように編集
ユアン・

1

単体テストは通常​​、ホワイトボックステストです(実際のコードにアクセスできます)。したがって、内部をある程度知っていてもかまいませんが、初心者は内部動作をテストする必要がないため(「メソッドを最初に呼び出してから、次にbを呼び出し、次にaを再度呼び出す」など)、初心者の方が簡単ではありません。

クラス(ユニット)はその外部データ(またはデータプロバイダー)に依存するため、データを提供するモックを挿入することは問題ありません。ただし、結果を確認する必要があります(それらに到達する方法ではありません)。たとえば、Personインスタンスを提供し、電子メールが正しい電子メールアドレスに送信されたことを確認します。たとえば、電子メールのモックも提供します。モックは、テストで後でアクセスできるように、宛先の電子メールアドレスを保存するだけです-コード。(マーティン・ファウラーはモックではなくスタブと呼んでいますが)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.