今日、私は同僚と、クラスのプライベートメンバーまたはプライベートステートをテストするかどうかについて話し合いました。彼はそれが理にかなっている理由を私にほとんど確信させました。この質問は、プライベートメンバーをテストする性質と理由に関する既存のStackOverflowの質問を複製することを目的としていません。たとえば、ユニットテストをテストしているクラスの友達にすることの何が問題になっていますか。
私の意見では、同僚の提案は、単体テストの実装クラスにフレンド宣言を導入するのに少し脆弱でした。私の意見では、テストされたコードの依存関係をテストコードに導入しているのに対し、テストコードはすでにテストされたコード=>循環依存関係に依存しているため、これは問題ありません。テストクラスの名前を変更するような無邪気なことでさえ、単体テストを壊し、テストされたコードのコード変更を強制します。
テンプレート関数を特殊化することが許可されているという事実に依存する他の提案について、C++の達人に判断してもらいたいと思います。クラスを想像してみてください。
// tested_class.h
struct tested_class
{
tested_class(int i) : i_(i) {}
//some function which do complex things with i
// and sometimes return a result
private:
int i_;
};
テスト可能にするためだけにi_のゲッターを用意するというアイデアは好きではありません。したがって、私の提案は、クラスでの「test_backdoor」関数テンプレート宣言です。
// tested_class.h
struct tested_class
{
explicit
tested_class(int i=0) : i_(i) {}
template<class Ctx>
static void test_backdoor(Ctx& ctx);
//some function which do complex things with i
// and sometimes return a result
private:
int i_;
};
この関数を追加するだけで、クラスのプライベートメンバーをテスト可能にすることができます。単体テストクラスやテンプレート関数の実装への依存関係はないことに注意してください。この例では、単体テストの実装はBoostTestフレームワークを使用しています。
// tested_class_test.cpp
namespace
{
struct ctor_test_context
{
tested_class& tc_;
int expected_i;
};
}
// specialize the template member to do the rest of the test
template<>
void tested_class::test_backdoor<ctor_test_context>(ctor_test_context& ctx)
{
BOOST_REQUIRE_EQUAL(ctx.expected_i, tc_.i_);
}
BOOST_AUTO_TEST_CASE(tested_class_default_ctor)
{
tested_class tc;
ctor_test_context ctx = { tc, 0 };
tested_class::test_backdoor(ctx);
}
BOOST_AUTO_TEST_CASE(tested_class_value_init_ctor)
{
tested_class tc(-5);
ctor_test_context ctx = { tc, -5 };
tested_class::test_backdoor(ctx);
}
まったく呼び出せない単一のテンプレート宣言を導入することで、テスト実装者にテストロジックを関数に転送する可能性を与えます。この関数は、タイプセーフコンテキストに作用し、テストコンテキストの匿名タイプの性質により、特定のテストコンパイルユニット内からのみ表示されます。そして最良のことは、テストされたクラスに触れることなく、好きなだけ匿名のテストコンテキストを定義し、それらのテストに特化できることです。
確かに、ユーザーはテンプレートの特殊化が何であるかを知っている必要がありますが、このコードは本当に悪いか、奇妙であるか、読めませんか?または、C ++開発者に、C ++テンプレートの特殊化とは何か、およびそれがどのように機能するかについての知識があることを期待できますか?
友達を使ってユニットテストクラスを宣言することについて詳しく説明しますが、これは堅牢ではないと思います。ブーストフレームワーク(または他のテストフレームワーク)を想像してみてください。テストケースごとに個別のタイプを生成します。しかし、私が書くことができる限り、なぜ私は気にする必要があります:
BOOST_AUTO_TEST_CASE(tested_class_value_init_ctor)
{
...
}
フレンドを使用する場合は、各テストケースをフレンドとして宣言する必要がありました...または、一般的なタイプ(フィクスチャなど)でテスト機能を導入し、フレンドとして宣言して、すべてのテスト呼び出しをそのタイプに転送することになります。 。それは奇妙ではありませんか?
私はあなたの賛否両論がこのアプローチを実践しているのを見たいと思います。