web-dev-qa-db-ja.com

単体テストを、それがテストしているクラスの友達にすることの何が問題になっていますか?

C++では、ユニットテストクラスを、テストしているクラスのフレンドにすることがよくあります。これは、プライベートメソッドの単体テストを作成する必要があると感じる場合があるためです。あるいは、オブジェクトの状態を簡単に設定してテストできるように、プライベートメンバーにアクセスしたい場合があります。私はクラスのパブリックまたは保護されたインターフェイスを変更していないので、カプセル化と抽象化を維持するのに役立ちます。

サードパーティのライブラリを購入した場合、ベンダーが単体テストをしたいという理由だけで、私が知る必要のない多数のパブリックメソッドによって、そのパブリックインターフェイスが汚染されることを望まないでしょう。

また、クラスから継承するかどうかを知る必要がない、保護されたメンバーの束について心配する必要もありません。

これが、抽象化とカプセル化を維持する理由です。

私の新しい仕事では、彼らはユニットテストのためでさえも友達クラスを使うことに反対しました。彼らは、クラスがテストについて何も「知っている」べきではなく、クラスとそのテストの密結合を望まないのでと言います。

誰かが私にこれらの理由をもっと詳しく説明して、理解を深めることができますか?単体テストに友達を使うことがなぜ悪いのか、私にはわかりません。

57
cchampion

理想的には、プライベートメソッドをユニットテストする必要はまったくないはずです。クラスのコンシューマが注意する必要があるのはパブリックインターフェースだけなので、それをテストする必要があります。プライベートメソッドにバグがある場合は、クラスのパブリックメソッドを呼び出すユニットテストによって検出され、最終的にバグのあるプライベートメソッドが呼び出されます。バグがうまく管理できなかった場合、これは、テストケースが、クラスに実装させたいコントラクトを完全に反映していないことを示しています。この問題の解決策は、ほとんどの場合、より詳細に調べてパブリックメソッドをテストすることであり、テストケースでクラスの実装の詳細を調べないことです。

繰り返しますが、これは理想的なケースです。現実の世界では、物事は必ずしもそれほど明確ではない場合があり、単体テストクラスを、それがテストするクラスの友だちにすることは、許容できる、または望ましい場合さえあります。それでも、それはおそらくあなたがいつもやりたいことではありません。頻繁に表示される場合は、クラスが大きすぎるか、実行しているタスクが多すぎる可能性があります。その場合は、プライベートメソッドの複雑なセットを個別のクラスにリファクタリングすることでそれらをさらに分割すると、ユニットテストで実装の詳細を知る必要がなくなります。

57
bcat

テストするさまざまなスタイルとメソッドがあることを考慮する必要があります。 ブラックボックステスト は、パブリックインターフェイスのみをテストします(クラスをブラックボックスとして扱います)。抽象基本クラスがある場合は、すべての実装に対して同じテストを使用することもできます。

ホワイトボックステスト を使用する場合、実装の詳細を確認することもできます。クラスが持つプライベートメソッドだけでなく、どのような条件ステートメントが含まれるか(つまり、条件のコーディングが難しいことがわかっているために条件カバレッジを増やしたい場合)。ホワイトボックステストでは、クラス/実装と、テストではなく実装をテストするために必要なテストとの間に「高い結合」があります。

Bcatが指摘したように、多くのプライベートメソッドの代わりに、コンポジションとそれよりも小さいクラスを使用すると便利な場合があります。これにより、テストケースをより簡単に指定して適切なテストカバレッジを取得できるため、ホワイトボックステストが簡素化されます。

12
Philipp

Bcatは非常に良い答えを出したと思いますが、彼が示唆している例外的なケースについて詳しく説明したいと思います

現実の世界では、物事は必ずしもそれほど明確ではない場合があり、単体テストクラスを、それがテストするクラスの友だちにすることは、許容できる、または望ましい場合さえあります。

私は大規模なレガシーコードベースを持つ会社で働いています。これには2つの問題があり、どちらも友達の単体テストを望ましいものにするのに役立ちます。

  • リファクタリングを必要とするわいせつな関数やクラスに悩まされていますが、リファクタリングを行うには、テストを行うと役立ちます。
  • 私たちのコードの多くはデータベースアクセスに依存しています。データベースアクセスはさまざまな理由で単体テストに組み込むべきではありません。

後者の問題を緩和するためにモッキングが役立つ場合もありますが、多くの場合、これは不必要に複雑な設計(他に何も必要とされないクラス階層)につながるだけですが、次の方法で非常に単純にコードをリファクタリングできます。

class Foo{
public:
     some_db_accessing_method(){
         // some line(s) of code with db dependance.

         // a bunch of code which is the real meat of the function

         // maybe a little more db access.
     }
}

これで、関数の大部分がリファクタリングを必要とする状況になったので、単体テストが必要です。公開しないでください。さて、この状況で使用できるモッキングと呼ばれる素晴らしい技術者がいますが、この場合、モックはやり過ぎです。階層を不必要にして、設計をより複雑にする必要があります。

より実用的なアプローチは、次のようなことです。

 class Foo{
 public: 
     some_db_accessing_method(){
         // db code as before
         unit_testable_meat(data_we_got_from_db);
         // maybe more db code.    
 }
 private:
     unit_testable_meat(...);
 }

後者を使用すると、ユニットテストから必要なすべての利点が得られます。たとえば、肉のコードをリファクタリングするときに生成されるエラーを検出するための貴重なセーフティネットが提供されます。ユニットテストを行うには、UnitTestクラスをフレンドにする必要がありますが、Mockを使用できるようにするためだけに、他の方法では役に立たないコード階層よりもはるかに優れていると強く主張します。

これはイディオムになるはずだと思います。これは、単体テストのROIを高めるための適切で実用的なソリューションだと思います。

9
Spacemoose

Bcatが提案するように、可能な限り、パブリックインターフェイス自体を使用してバグを見つける必要があります。ただし、プライベート変数の印刷や期待される結果との比較などを行う場合(開発者が問題を簡単にデバッグするのに役立つ)、テストするクラスのフレンドとしてUnitTestクラスを作成できます。ただし、以下のようなマクロの下に追加する必要がある場合があります。

class Myclass
{
#if defined(UNIT_TEST)
    friend class UnitTest;
#endif
};

ユニットテストが必要な場合にのみ、フラグUNIT_TESTを有効にします。他のリリースでは、このフラグを無効にする必要があります。

5
bjskishore123

多くの場合、フレンドユニットテストクラスの使用に問題はありません。はい、大きなクラスを小さなクラスに分解する方が良い方法です。私は人々がこのようなもののためにフレンドキーワードを使用して却下するのが少し早すぎると思います-それは理想的なオブジェクト指向設計ではないかもしれませんが、それが本当に必要な場合は、より良いテストカバレッジのために少し理想主義を犠牲にすることができます。

4
Dave K

通常、パブリックインターフェイスのみをテストするため、実装を自由に再設計およびリファクタリングできます。プライベートメンバーのテストケースを追加すると、クラスの実装に関する要件と制限が定義されます。

1
Inverse

テストする機能を保護します。単体テストファイルで、派生クラスを作成します。テスト対象のクラスの保護された関数を呼び出すパブリックラッパー関数を作成します。

0
klaas-jan