web-dev-qa-db-ja.com

ユニットテストでは、クラスごとに1つのインターフェイスが必要ですか?

すべてのパブリックビヘイビアークラス(データクラスを除く)のインターフェイスを定義する必要がありますか?

明確な答えを見つけるために何時間もかけて検索と読書をしました。 「すべてのパブリッククラスのインターフェースを定義していますか」と検索すると、回答の90%はそうすべきではないと答えています。ただし、そのようなコメントをまだ見つけていないため、単体テストを行う方法を説明しています。

Moqは具象クラスをモックできると言いますが、virtualと宣言されたメンバーに対してのみであり、コンストラクターをvirtualとマークできないため、Moqがコンストラクターでコードが実行されるのを防ぐ方法はありません(AFAIK)。すべてのクラスのすべてのメンバーを仮想としてマークすることに関心はありません。

この質問への回答は3つのカテゴリに分類されるようです。

  • すべてをテストする人
  • コードの一部のみをテストする人
  • 単体テストを気にしない人

私はすでに両側ですべての議論を見てきましたが、コードの広範囲な単体テストを行う他の良い方法はまだ見ていません。では、なぜ90%の人がこれに反対しているのでしょうか。

今のところ、インターフェイスを#region内の同じファイルの上部に配置することで、ファイルが増えることはなく、実装に簡単に移動でき、コードが煩雑になりません。見る。いくつかのインターフェースを複数回実装する必要がある場合、後で別のファイルに移動することを妨げるものは何もありません。

このようなインターフェースを作成する主な理由の1つは、モックフレームワークの制限によるものです。 .NETの次のバージョンで、モックフレームワークが非仮想メソッドをモックすることを許可したとしましょう。それらのインターフェイスを作成する必要がありますか?

簡単な例をとると、クラスA、B、Cがあります。Aは、テストのためにIBとICに依存しています。モックに必要ない場合でも、Aには依存関係注入によって注入されたBおよびCのインスタンスが必要です。依存関係の注入ではインターフェースの使用はオプションですが、インターフェースを使用しないことを推奨する良い例はまだ見ていません。それで、モックする必要がないというこの架空のシナリオでは、それらのインターフェースを作成する必要があるのでしょうか。

そして最後に、そのようなインターフェイスを作成する場合isは良いアプローチです(多くの人はこれに同意しません)、コンパイル時にこれらのインターフェイスを自動生成できるツールがあるため、メソッドをコピーする必要はありません。署名とコメントは常に?

7

根本的な問題は、単体テストでのモックの必要性です。モッキングはコードのにおいです-場合によっては避けられませんが、単体テストに蔓延するべきではありません。

外部サービスへのインターフェースであるクラス、入出力を実行するクラス、または非決定的(時間、乱数生成)クラスのモッキングが必要です。ただし、コード内のすべてのクラスをモックする必要はありません。これを行うと、テストが不安定になり、インターフェースのすべての変更により、対応するテストモックを更新する必要が生じ、単体テストの目的が損なわれます。

Moqのようなモックフレームワークは、テスト用に設計されていない既存のコードにテストを追加する必要があるレガシーコードベースの場合にのみ使用する必要があります。適切に設計されたコードでは必要ありません。

単体テストでは単一のクラスのみをテストする必要があるという懸念に対処するには、これは誤解です。単体テストでは、独立してテストできるコードのユニットをテストする必要があります。これは、コードがどの程度緊密に結合されているかに応じて、単一の関数または一連のクラスの場合があります。たとえば、Math.Max()には依存関係や副作用がないため、モックする必要なく独立してテストできます。しかし、実際にMaxがいくつかのヘルパークラスを内部で使用しているかどうかは気にしません。パブリックビヘイビアが指定された要件に対応している限り、これは実装の詳細です。

理想的には、コードをMaxのように機能させる必要があります。パブリックな依存関係や副作用のない明確に定義されたインターフェース。これには次の2つの利点があります。

  • Maxをテストするために、いかなる形式のモックも必要ありません。
  • 他のコンポーネントがMaxに依存している場合は、モックする必要はありません。

(その意味で、静的メソッドはテストに適さないという神話を払拭しましょう。Math.Max()が示すように、副作用のない静的メソッドは、実際にはテストにとって最高のものです。静的は避けてください状態。)

もちろん、I/Oと副作用はどのコードでも必要です。しかし、これを、単体テストを必要とする複雑なロジックから分離する必要があります。

私はクラスA、B、およびCを持っています。AはテストのためにIBおよびICに依存しています。モックに必要ない場合でも、Aには依存関係注入によって注入されたBおよびCのインスタンスが必要です。

A B Cの例の問題は、答えはA、B、Cが実際に何であるかに依存することです。いくつかの例を見てみましょう:

AはInvoiceGenerator、Bは顧客、Cは注文です。簡単-テストに適したデータで顧客と注文をインスタンス化します。あざける必要はありません。

ただし、AがInvoiceGeneratorAndSenderで、BがSmtpServerである場合、一時的にSmtpServerをモックする必要があるかもしれませんが、請求書生成をメーリングから分離する方が良いでしょう。その後、モックを作成する必要なく、請求書の生成をテストできます。メーリングはSmtpServerの非常に浅いラッパーになるため、ユニットテストをまったく行う必要がありません。 (ただし、統合テストが必要です。)

7
JacquesB

すべてのパブリックビヘイビアークラス(データクラスを除く)のインターフェイスを定義する必要がありますか?

必ずしも。

明確な答えを見つけるために何時間もかけて検索と読書をしました。 「すべてのパブリッククラスのインターフェースを定義していますか」と検索すると、回答の90%はそうすべきではないと答えています。ただし、そのようなコメントをまだ見つけていないため、単体テストを行う方法を説明しています。

短い答え:具体的な依存関係を「単体テスト」することができます。

より長い答え:インターフェイスを実装から分離することで、2つを分離することができます。同じアイデアを表現する別の方法は、インターフェースが代替実装を導入するために使用できる継ぎ目を提供することです。

Parnas の言語では、インターフェイスにより、特定の機能を実装する方法の決定からモジュールを隔離できます。

予測可能な動作は共有の可変状態から切り離されることに依存しており、高速実行には低速コードを回避する必要があるため、テスト、特に開発者テストでは代替を頻繁に使用する傾向があります。

しかし、特定のコンポーネントがすでに共有された可変状態から分離されていて、低速のコードが含まれていない場合、テストでそれを使用するだけではないという主張は非常に弱くなります。

Dependency Inversion Principle を適用すると、興味深いことが起こります-インターフェースの定義は、実装。

つまり、実装がインターフェースをサポートしているからではなく、コンシューマーがインターフェースをサポートしているからです。

class Example {
    void fizz (SomeInterface x) {...}
    void buzz (SomeImplementation x) {...}
}

これは、実装のconsumerの例であり、ニーズが実際にどの程度柔軟であるかを決定します。

詳細については、 依存性注入が依存性逆転原理と同じではない を参照してください。

したがって、テストを作成していて、具体的な実装を別の実装に簡単に置き換えられることを望んでいる場合、それはコードの匂いであり、テストサブジェクトのインターフェイスがコラボレーターの実装に緊密に結合されていることを示しています。だから最初にそれを修正してください...

また、コードで「new」キーワードを使用しないでくださいと言う人も何人かいました。

それは正しくありません。ランタイムにデータをヒープに格納するように要求している場合は、新しいsomewhereを呼び出すことになります。彼らが示唆しているのは、新しい演算子をlogic;に混在させないことです。オブジェクトのインスタンスをインスタンス化する方法の決定は、それ自体のモジュール(別名「ファクトリ」)に移動できます。

移動する必要がありますか?多分;ここで2つのプレッシャーがあります。コードの存続期間中にインスタンス化が変更される可能性がある場合、別のモジュールを使用すると、変更の影響が制限されます。ある環境から別の環境に移動するときに実装を置き換える場合(例:テストと本番)、それはreplaceableそのコードを分離するモジュール。

ただし、それがDateTime、TimeSpan、StringBuilderなどのクラスに適用されるかどうかは指定しないでください。

正しい。私の答えは、バグをキャッチする負担を共有するために多くの眼球を獲得する安定した依存関係がある場合、それは可変状態または命令型シェルへの間接的な依存関係を導入せず、変化させたいものではないということですある環境から別の環境へ、そして私たちの「ユニット」をこの依存性から分離するためにたくさんの儀式を導入することは、時間の無駄であり、豚を悩ませます。

したがって、StringBuilderは、「もちろん、そのまま使用できます」のように、Stringが配列と同じバケットに入るのと同じバケットに入ります。後で一部のコンテキストでStringをRopeに置き換える必要がある場合は、問題ありません。

これらのクラス間で依存性注入を使用する必要がありますか?

コースの馬-場合によっては、期待される実装が分離されている場合でも、依存関係を置き換え可能な引数として渡すことが理にかなっています。あなたが決定しなかったからといってプルリクエストを拒否するつもりはありません。調査している問題について私の限られた理解しか与えていなかったでしょう。しかし、選択の動機を説明するメモを含めるようにお願いするかもしれません。 。

7
VoiceOfUnreason

すべてのパブリックビヘイビアークラス(データクラスを除く)のインターフェイスを定義する必要がありますか?

このように考えるのをやめるべきです。

Fooを持っていると判断してから、IFooインターフェースが必要だと判断しないでください。

Fooとは何かを考えるのをやめ、Fooを何が使用するか、どのように使用するか、どのように使用しないかについて考えます。

インターフェイス、抽象クラス、構成デリゲーター、およびその他の形式の抽象化は、何かが何であるかを考慮して設計されるべきではありません。それは、何かがどのように使用されるかを中心に設計する必要があります。

最も重要なのは、使用を行っていることは何でも、抽象化がどのような形を取るかを知らないはずです。 Fooがキーワードインターフェイスであるか、抽象クラスであるか、またはコンポジションへの委譲であるかどうかを知る必要はありません。

知らないということは、あなたが好きなようにそれをするのを自由にします。それはあなたの心を変えることができます。それはあなたがすべてを壊すのを防ぎます。あなたがこれをすればあなたの質問への答えは重要ではないので、それはあなたの質問を無意味なものにします。

これは、常に単体テスト、場合によっては単体テスト、または単体テストをまったく行わない場合に当てはまります。これは単体テストに関するものではありません。これは、必要以上に知ることから物事を保護することです。これを行うと、インターフェイスの規則を確立する必要があるように感じなくなります。

4
candied_orange

人々がそれに反対する理由の一部は、コスト/利益分析としてです。十分に確立されたコードベースで作業している場合、システムは機能している(顧客が5年間使用している)と確信していますが、単体テストのカバレッジは不十分です。この場合、単体テストを作成するだけでなく、簡単に単体テストできるようにコードをリファクタリングするのにかかる時間と、そうすることの利点を比較検討する必要があります。このような確立されたコードベースでは、単体テストの主な理由の1つは、回帰テストとリファクタリングを支援することです。これを行うために機能しているがテストが不十分なコードベースをリファクタリングする必要がある場合は、自分で頭痛の種を作っている可能性があります。

繰り返しますが、これは非常に多くの要因に依存します。頻繁に変更されるコードには、10年間経験されていないコードよりもはるかに広範な回帰テストが必要です。

緑のフィールドの真新しい専門的なプロジェクトでは、すべてを広範囲にユニットテストすることを常に強くお勧めします。しかし、レガシーシステムは通常、企業が利益を上げるのを助けるために維持されているため、単体テストを追加することがその目標の達成に役立つか妨げるかを慎重に検討する必要があります。

1
Gus314

これには2つの答えが必要です:

  • 開発中およびテスト中に役立つため、コードを分離することをお勧めします

  • 実装をコピーするインターフェースは、背後にある実際の抽象化を設計していないため、においがします。 CarICarはビジネスの説明を提供しないため、匂いがします。インターフェイスCarと、 Turism[〜#〜] suv [〜#〜]PickupTruck ...(これは、継承を別の方法として使用する場合もあります抽象化の)

0
A Bravo Dev