web-dev-qa-db-ja.com

モデルクラスでのデカップリングの実現

Modelクラスをテストドライブ(または少なくとも単体テストを作成)しようとしていますが、クラスが結合しすぎていることに気付きました。この結合を壊すことができないので、ユニットテストを書くことはますます難しくなっています。

具体的には:

モデルクラス:これらは私のアプリケーションのデータを保持するクラスです。それらはPOJO(プレーンな古いJavaオブジェクト))によく似ていますが、いくつかのメソッドもあります。アプリケーションが大きすぎないため、約15のモデルクラスがあります。

カップリング:例を挙げると、注文ヘッダー->注文アイテムの単純なケースを考えてみてください。ヘッダーはアイテムを認識し、アイテムはヘッダーを認識します(特定の操作を実行するには、ヘッダーからの情報が必要です)。次に、注文アイテム->アイテムレポートの間に関係があるとします。アイテムレポートにはアイテムも必要です。この時点で、アイテムレポートのテストを書くことを想像してください。テストを実行するには、注文ヘッダーが必要です。これは3つのクラスを持つ単純なケースです。クラスが増えると、事態はさらに複雑になります。

アルゴリズム、永続化レイヤー、UIインタラクションなどを設計するときに分離されたクラスを思い付くことができますが、モデルクラスでは、それらを分離する方法を考えることができません。彼らは現在、相互に依存するクラスの大きな塊として座っています。

これが私が考えることができるいくつかの回避策です:

  1. データジェネレータ:モデルクラスのサンプルデータを生成するパッケージがあります。たとえば、OrderHeaderGeneratorクラスは、いくつかの基本データを含むOrderHeadersを作成します。 ItemReport単体テストのOrderHeaderGeneratorを使用して、OrderHeaderクラスのインスタンスを取得します。問題は、これらのジェネレーターが非常に速く複雑になることです。次に、これらのジェネレーターもテストする必要があります。目的を少し破る。
  2. 依存関係の代わりにインターフェース:ハードな依存関係を取り除くためのインターフェースを考え出すことができます。たとえば、OrderItemクラスはIOrderHeaderインターフェイスに依存します。そのため、単体テストでは、IOrderHeaderインターフェイスを実装するFakeOrderHeaderクラスを使用してOrderHeaderの動作を簡単にモックできます。このアプローチの問題は、Modelクラスが持つ複雑さです。

モデルクラスでこの結合を解除する方法について他のアイデアはありますか?または、モデルクラスの単体テストを簡単にする方法は?


回答を確認してから更新:

  • 私はこの問題についてもう少し考えてみましたが、これらの依存関係の一部は本当に必要ではないことに気付きました(ほとんどの回答が指摘しているように)。それらが存在した理由は、私のUIレイヤーがそれを必要としていたためです。基本的に、UIレイヤーが子からヘッダーに簡単に移動し、いくつかのヘッダーフィールドを子レベルのビューに表示できるように、これらの依存関係を挿入しました。今それを見ると、とてもシンプルに見えます。しかし、そもそもなぜこれらの依存関係が存在するのかを理解するのに少し時間がかかりました。これで、UIは基本的に、子レベルのビューに移動するときにヘッダーをコンテキスト内に保持します。
  • 一方、インターフェイスを介して依存関係の一部を削除しました。これらの依存関係はほとんど1対1でした。たとえば、HeaderとHeaderReportの間。以前は、HeaderReportは情報(アイテム、日付など)を取得するためにHeaderに依存していたため、HeaderReportのテストは非常に困難でした。現在、HeaderReportはHeaderDataProviderインターフェイスに依存しており、完全な分離を実現できます。 HeaderReportは、HeaderDataProviderインターフェイスのスタブクラスを使用して非常に簡単にテストできます。
  • これらの抽象化(UIレイヤー-モデルレイヤー-バックエンドレイヤー)を作成するのは簡単に見えますが、実際には、かなりの練習が必要です!
6
Guven

クラスは本当に分離する必要がありますか?あなたの例では、ビジネスの観点から、注文ヘッダーと注文アイテムはエンティティであり、非常に近いものです。それらはおそらく aggregate を形成します。したがって、それらを分離しようとすると、不必要な複雑さが生じ、モデルの読み取りが混乱します。

これを解決するには、コード内で集計を見つけて、集計全体を単体テストします。

5
Euphoric

話を簡単にするためにシナリオを簡略化しようとしているのはわかっていますが、なぜOrderItemはヘッダーを気にするのですか?ヘッダーはアグリゲーター(DDDでは「ルート」と呼ばれます)であり、そのコンポーネント(OrderItemはその1つです)について知る責任があります。

たとえば、特定のアイテムが10個以上含まれている注文に割引が適用されるなどの特殊なケースについて話し合っている場合、注文の処理中にどの割引が適用されるかを見つける割引サービスなどの仕事になります。

同じことがItemReportにも当てはまります...それは、アイテムの選択、フィルタリング、順序付けなどのルールを知る責任があります。アイテムはレポートに関係するべきではありません。それはその仕事ではありません。

6
Michael Brown

モデルクラスでこの結合を解除する方法について他のアイデアはありますか?または、モデルクラスの単体テストを簡単にする方法は?

私の意見では、インターフェースに対するコーディングが進むべき道です。言い換えれば、設計によってコードを分離することは、現代のプロジェクトが従うアプローチです。これは、nit-testingのニーズに大いに役立ちます。

言い換えると、開発を計画している、または依存関係を削除するために分離しようとしているソフトウェアコンポーネントに対して、正式で正確かつ検証可能なインターフェイス仕様を定義することをお勧めします。

1
Yusubov

これを行う1つの方法は、あなたが言ったように、クラス間にインターフェースを作成することです。各クラスは、その依存関係を、単体テストコンテキストでモックまたはスタブできるインターフェイス参照として受け取ります。これにより、各クラスに必要な設計のessential部分についてもう少し考えるようになるかもしれません。たとえば、注文アイテムに完全な注文ヘッダーが必要ない場合があります。このプロセスでは、設計の潜在的な概念を発見する可能性があります。これにより、再利用の可能性が高まり、意図に沿った要素がよりインラインで作成されます。またはraison d'être

一方、インターフェースを作成することは、そのような相乗的で結合された概念にとっては奇妙に思えるかもしれません。その場合は、 流暢なテストデータビルダー を使用して、ドメイン固有の直接的な方法を作成し、依存オブジェクトを簡単に作成できます。これらのビルダーは、依存オブジェクトを作成するためのテストコードによって使用されるため、直接テストする必要はありません。

1
Jordão