フレンドクラスを使用したC ++の単体テストプライベートメソッド


15

これは議論の余地があることを知っていますが、これが私にとって最良の選択肢であると仮定しましょう。私はこれを行うための実際のテクニックは何なのかと思っています。私が見るアプローチはこれです:

1)テストするメソッドのクラスのフレンドクラスを作成します。

2)friendクラスで、テストされたクラスのプライベートメソッドを呼び出すパブリックメソッドを作成します。

3)friendクラスのパブリックメソッドをテストします。

上記の手順を説明する簡単な例を次に示します。

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

編集:

私のコードベースについて人々が疑問に思っている答えの1つに続く議論で私はわかります。

私のクラスには、他のメソッドによって呼び出されるメソッドがあります。これらのメソッドはいずれもクラスの外部で呼び出されるべきではないため、プライベートにする必要があります。もちろん、これらを1つの方法に入れることもできますが、論理的にははるかに優れています。これらのメソッドは単体テストを保証するほど複雑であり、パフォーマンスの問題のため、これらのメソッドをリファクタリングする必要がある可能性が非常に高いため、リファクタリングが何も壊さないことを確認するテストを行うとよいでしょう。テストを含めてこのプロジェクトに取り組んでいるのは私だけですが、チームで作業しているのは私だけではありません。

上記のことを言っても、私の質問は、プライベートメソッドの単体テストを書くのが良い習慣かどうかではありませんでしたが、フィードバックに感謝しています。


5
ここに疑わしい質問に対する疑わしい提案があります。リリースされたコードはテストについて知っている必要があるため、友人の結合は好きではありません。以下のNirの答えはそれを軽減する1つの方法ですが、テストに適合するようにクラスを変更することはまだ好きではありません。私は頻繁に継承に依存しないため、プライベートメソッドを保護し、必要に応じてテストクラスを継承して公開することがあります。このコメントに対して少なくとも3つの「ブーイング」を期待していますが、実際には、パブリックAPIとテストAPIは異なる場合があり、プライベートAPIとは異なる場合があります。えー
Jトラナ14年

4
@JTrana:それを適切な答えとして書いてみませんか?
バートヴァンインゲンシェナウ14年

OK。これはあなたがとても誇りに思うものの一つではありませんが、うまくいけば助けになるでしょう。
J Trana

回答:


23

私が頻繁に使用する友人(ある意味では)の代替は、access_byとして知られるようになったパターンです。とても簡単です。

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

ここで、クラスBがAのテストに関与しているとします。これを書くことができます。

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

その後、このaccess_byの特殊化を使用して、Aのプライベートメソッドを呼び出すことができます。基本的に、これは、Aのプライベートメソッドを呼び出したいクラスのヘッダーファイルにフレンドシップを宣言する責任を負います。また、Aのソースを変更せずに友人をAに追加できます。慣用的には、Aのソースを読む人には誰でも、Aがそのインターフェースを拡張するという意味でBを真の友人ではないことを示します。むしろ、Aのインターフェースは与えられたとおりに完全であり、BはAへの特別なアクセスを必要とします(テストが良い例であり、ブーストPythonバインディングを実装するときにこのパターンを使用しました。C++でプライベートである必要がある関数は実装のためにPythonレイヤーに公開します)。


を作成するための有効なユースケースfriend access_byに興味があり、最初の非友人では十分ではありません-A内のすべてにアクセスできるネストされた構造体ですか?例えば。coliru.stacked-crooked.com/a/663dd17ed2acd7a3
ピリッ

10

テストするのが難しい場合、それはひどく書かれています

独自のテストを保証するほど複雑なプライベートメソッドを持つクラスがある場合、そのクラスはやり過ぎです。中には出ようとする別のクラスがあります。

テストするプライベートメソッドを1つまたは複数の新しいクラスに抽出し、パブリックにします。新しいクラスをテストします。

コードをテストしやすくすることに加えて、このリファクタリングはコードの理解と保守を容易にします。


1
私はこの答えに完全に同意します。パブリックメソッドをテストしてプライベートメソッドを完全にテストできない場合は、何かが正しくなく、プライベートメソッドを独自のクラスにリファクタリングするのが良い解決策になります。
デビッドパーフォース14年

4
私のコードベースでは、クラスには計算グラフを初期化する非常に複雑なメソッドがあります。これはさまざまな側面を実現するために、いくつかのサブ機能を順番に呼び出します。各サブ関数は非常に複雑であり、コードの合計は非常に複雑です。ただし、サブクラスは、このクラスで正しい順序で呼び出されなければ意味がありません。ユーザーが気にするのは、完全に初期化される計算グラフです。中間体はユーザーにとって価値がありません。どうか、これをどのようにリファクタリングするべきか、なぜプライベートメソッドをテストするよりも理にかなっているのかを聞きたいと思います。
ニルフリードマン14年

1
@Nir:ささいなことをする:それらのすべてのメソッドをパブリックにしてクラスを抽出し、既存のクラスを新しいクラスのファサードにします。
ケビンクライン14年

この答えは正しいですが、実際に作業しているデータに依存します。私の場合、実際のテストデータは提供されないため、リアルタイムデータを観察してアプリケーションに「注入」し、その方法を確認する必要があります単一のデータは複雑すぎて処理できないため、リアルタイムデータを実際に再現するよりも、ターゲットとするリアルタイムデータのサブセットのみである部分テストデータを人為的に作成する方がはるかに簡単です。プライベート関数は複雑ではありません。他のいくつかの小さなクラス(それぞれの機能を備えた)の実装を呼び出すのに十分
-rbaleksandar

4

プライベートメソッドをテストするべきではありません。限目。クラスを使用するクラスは、機能するために内部で使用するメソッドではなく、クラスが提供するメソッドのみを考慮します。

コードカバレッジが心配な場合は、パブリックメソッド呼び出しの1つからそのプライベートメソッドをテストできる構成を見つける必要があります。それができないなら、そもそもメソッドを持っている意味は何ですか?それは単に到達不能なコードです。


6
プライベートメソッドのポイントは、開発を容易にすることです(懸念の分離、DRYの維持、または任意の数の物事を通じて)が、永続的でないことを意図しています。彼らはそのためにプライベートです。それらは、実装ごとに機能が大幅に出現、消滅、または変化する可能性があります。そのため、ユニットテストにそれらを結び付けることは、常に実用的ではなく、有用でさえありません。
アンプト14年

8
常に実用的または有用であるとは限りませんが、それをテストするべきではないと言うのは非常に遠い話です。プライベートメソッドについては、他の誰かのプライベートメソッドであるかのように話しています。「彼らは現れたり消えたりするかもしれない...」。いいえ、できませんでした。それらを直接単体テストしている場合は、自分で保守しているという理由だけが必要です。実装を変更すると、テストも変更されます。要するに、あなたの毛布文は不当です。これについてOPに警告するのは良いことですが、彼の質問はまだ正当化されており、あなたの答えは実際には答えていません。
ニルフリードマン14年

2
また、OPが議論された慣行であることを認識していることを前もって述べました。とにかく彼がそれをやりたいと思ったら、多分彼は本当にそれに対する正当な理由があるのでしょうか?どちらも彼のコードベースの詳細を知りません。私が使用しているコードには、非常に経験豊富で熟練したプログラマーがおり、場合によってはプライベートメソッドの単体テストが役立つと考えていました。
ニルフリードマン14年

2
-1、「プライベートメソッドをテストするべきではない」などの回答。私見は役に立たないです。プライベートメソッドをテストするタイミングとテストしないタイミングに関するトピックは、このサイトで十分に説明されています。OPは、彼がこの議論に気づいていることを示しており、明らかに、彼の場合はプライベートメソッドをテストすることが道であるという仮定の下で解決策を探しています。
Doc Brown 14年

3
ここでの問題は、これが非常に古典的なXY問題である可能性があると思います.OPは何らかの理由でプライベートメソッドをユニットテストする必要があると考えているため、実際にはより実用的な観点からテストにアプローチでき、プライベートを見てメソッドは、クラスのエンドユーザーとの契約であるパブリックメソッドの単なるヘルパー関数です。
アンプト14年

3

これを行うにはいくつかのオプションがありますが、モジュールのパブリックインターフェイスを(本質的に)変更して、内部実装の詳細にアクセスできるようにすることを念頭に置いてください(効果的にユニットテストを密結合クライアント依存関係に変換します。依存関係はまったくありません)。

  • テストしたクラスにフレンド(クラスまたは関数)宣言を追加できます。

  • テストしたコードを実行する#define private public前に、テストファイルの先頭に追加でき#includeます。ただし、テストされたコードが既にコンパイルされたライブラリの場合、これによりヘッダーが既にコンパイルされたバイナリコードと一致しなくなる可能性があります(そしてUBが発生します)。

  • テストしたクラスにマクロを挿入し、後日そのマクロの意味を決定できます(コードをテストするための別の定義を使用)。これにより、内部をテストできますが、サードパーティのクライアントコードがクラスにハッキングすることもできます(追加する宣言で独自の定義を作成することにより)。


2

ここに疑わしい質問に対する疑わしい提案があります。リリースされたコードはテストについて知っている必要があるため、友人のカップリングは好きではありません。Nirの答えはそれを軽減する1つの方法ですが、テストに準拠するようにクラスを変更することはまだ好きではありません。

私は頻繁に継承に依存しないため、プライベートメソッドを保護し、必要に応じてテストクラスを継承して公開することがあります。現実には、パブリックAPIとテストAPIは異なる場合がありますが、プライベートAPIとは異なるため、バインドのようなものになります。

これが私がこのトリックに頼る種類の実用的な例です。私は埋め込みコードを作成しますが、ステートマシンにかなり依存しています。外部APIは必ずしも内部ステートマシンの状態を知る必要はありませんが、テストでは(おそらく)デザインドキュメントのステートマシン図への適合性をテストする必要があります。「現在の状態」のゲッターを保護されているものとして公開し、テストへのアクセスを許可して、ステートマシンをより完全にテストできるようにします。このタイプのクラスは、ブラックボックスとしてテストするのが難しいことがよくあります。


これはJavaのアプローチですが、プライベートパッケージをデフォルトレベルにして、同じパッケージ内の他のクラス(テストクラス)がそれらを見ることができるようにすることは非常に標準的です。

0

友人を使わなくても済むように、多くの回避策を使ってコードを書くことができます。

クラスを記述できますが、プライベートメソッドはまったくありません。その後、必要なことは、コンパイルユニット内で実装関数を作成し、クラスでそれらを呼び出して、アクセスする必要のあるデータメンバーを渡すことだけです。

そして、はい、将来ヘッダーを変更せずに署名を変更したり、新しい「実装」メソッドを追加したりできることを意味します。

価値があるかどうかにかかわらず、あなたは重量を量る必要があります。そして、あなたのヘッダーを誰が見るかによります。

サードパーティのライブラリを使用している場合、ユニットテスターへのフレンド宣言は表示されません。私も彼らのライブラリを構築したくないし、私がするときに彼らのテストを実行させたい。残念ながら、私が作成したサードパーティのオープンソースライブラリが多すぎます。

テストはライブラリの作成者の仕事であり、ユーザーの仕事ではありません。

ただし、すべてのクラスがライブラリのユーザーに表示されるわけではありません。多くのクラスは「実装」であり、適切に機能するための最良の方法で実装します。これらの場合、プライベートメソッドとメンバーはまだあるかもしれませんが、ユニットテスターに​​テストしてもらいたいです。したがって、堅牢なコードの迅速化につながる場合は、先に進んでそのようにしてください。そうする必要がある人にとっては保守が容易です。

クラスのユーザーがすべて自分の会社またはチーム内にいる場合、会社のコーディング標準で許可されていると仮定して、その戦略についてもう少しリラックスすることもできます。

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