web-dev-qa-db-ja.com

C ++クラスのプライベートメンバー(およびメソッド)でユニットテストを行う方法

私は単体テストに非常に慣れていないので、少し混乱しています。

VariableImplというC++クラスで(Boostユニットテストフレームワークを使用して)ユニットテストを実行しようとしています。詳細はこちら。

class Variable
{
public:
  void UpdateStatistics (void) {
    // compute mean based on m_val and update m_mean;
    OtherClass::SendData (m_mean);
    m_val.clear ();
  }
  virtual void RecordData (double) = 0;

protected:
  std::vector<double> m_val;

private:
  double m_mean;
};

class VariableImpl : public Variable
{
public:
  virtual void RecordData (double d) {
    // put data in m_val
  }
};

私の質問は、平均が正しく計算されていることを確認するにはどうすればよいですか? 1)m_meanは保護されており、2)UpdateStatisticsは別のクラスのメソッドを呼び出してから、ベクトルをクリアします。

私が見ることができる唯一の方法はゲッター(たとえば、GetMean)を追加することですが、私はこのソリューションがまったく好きではなく、最もエレガントだとは思いません。

どうすればよいですか?

また、プライベート変数ではなくプライベートメソッドをテストする場合はどうすればよいですか?

TIA、

ジル

33
Jir

nitテストはnitsをテストする必要があります。理想的には、すべてのクラスが自己完結型のユニットです。これは単一の責任の原則に直接従います。

したがって、クラスのプライベートメンバーをテストする必要はありません。クラスは、単体テストでそのままカバーできるブラックボックスです。

一方、これは常に正しいとは限らず、正当な理由がある場合もあります(たとえば、クラスのいくつかのメソッドが、テストする必要があるプライベートユーティリティ関数に依存している場合があります)。非常にシンプルで、非常に扱いにくいが、最終的には成功する解決策の1つは、ユニットテストファイルに次のコードを追加することですbeforeクラスを定義するヘッダーを含めます。

#define private public

もちろん、これはカプセル化を破壊しますevilです。しかし、テストに関しては、それは目的を果たします。

48
Konrad Rudolph

保護されたメソッド/変数の場合、クラスからTestクラスを継承してテストを実行します。

プライベートの場合は、友達クラスを紹介してください。それは最善の解決策ではありませんが、あなたのために仕事をすることができます。

またはこのハック

#define private public
11
DumbCoder

一般に、私は他の人がここで言ったことに同意します-単体テストする必要があるのはパブリックインターフェイスのみです。それでも、特定のテストケースに備えるために、最初に保護されたメソッドを呼び出さなければならない場合がありました。私は最初に上記の_#define protected public_アプローチを試しました。これはLinux/gccでは機能しましたが、Windows/VisualStudioでは失敗しました。その理由は、protectedpublicに変更すると、符号化されたシンボル名も変更され、リンカーエラーが発生したためです。ライブラリはprotected__declspec(dllexport) void Foo::bar()を提供しましたメソッドですが、_#define_を配置すると、テストプログラムでpublic__declspec(dllimport) void Foo::bar()メソッドが予期され、未解決のシンボルエラーが発生しました。

このため、私はfriendベースのソリューションに切り替え、クラスヘッダーで次のようにしました。

_// This goes in Foo.h
namespace unit_test {   // Name this anything you like
struct FooTester; // Forward declaration for befriending
}

// Class to be tested
class Foo 
{
  ...
private:
  bool somePrivateMethod(int bar);
  // Unit test access
  friend struct ::unit_test::FooTester;
};
_

そして、私の実際のテストケースでは、これを行いました:

_#include <Foo.h>
#include <boost/test/unit_test.hpp>
namespace unit_test {
// Static wrappers for private/protected methods
struct FooTester
{
  static bool somePrivateMethod(Foo& foo, int bar)
  {
    return foo.somePrivateMethod(bar);
  }
};
}

BOOST_AUTO_TEST_SUITE(FooTest);
BOOST_AUTO_TEST_CASE(TestSomePrivateMethod)
{
  // Just a silly example
  Foo foo;
  BOOST_CHECK_EQUAL(unit_test::FooTester::somePrivateMethod(foo, 42), true);
}
BOOST_AUTO_TEST_SUITE_END();
_

これは、Linux/gccおよびWindows/VisualStudioで動作します。

6
mindriot

C + +で保護されたデータをテストする良い方法は、フレンドプロキシクラスの割り当てです。

#define FRIEND_TEST(test_case_name, test_name)\
friend class test_case_name##_##test_name##_Test

class MyClass 
{
private:
  int MyMethod();
  FRIEND_TEST(MyClassTest, MyMethod);
};

class MyClassTest : public testing::Test 
{
public:
  // ...
  void Test1()
  {
    MyClass obj1;
    ASSERT_TRUE(obj1.MyMethod() == 0);
  }

  void Test2()
  {
    ASSERT_TRUE(obj2.MyMethod() == 0);
  }

  MyClass obj2;
};

TEST_F(MyClassTest, PrivateTests) 
{
 Test1();
 Test2(); 
}

その他のgoolgeテスト(gtest)を参照してください: http://code.google.com/p/googletest-translations/

3
photoscar

私の意見では、クラスのプライベートメンバー/メソッドをテストする必要性はコードの匂いですが、C++では技術的に実現可能だと思います。

例として、パブリックコンストラクター以外のプライベートメンバー/メソッドを持つDogクラスがあるとします。

#include <iostream>
#include <string>

using namespace std;

class Dog {
  public:
    Dog(string name) { this->name = name; };

  private:
    string name;
    string bark() { return name + ": Woof!"; };
    static string Species;
    static int Legs() { return 4; };
};

string Dog::Species = "Canis familiaris";

何らかの理由で、プライベートなものをテストしたいと思います。 privablic を使用してそれを実現できます。

privablic.h という名前のヘッダーを、希望する実装と一緒に含めます。

#include "privablic.h"
#include "dog.hpp"

次に、インスタンスメンバーのタイプに従っていくつかのスタブをマッピングします

struct Dog_name { typedef string (Dog::*type); };
template class private_member<Dog_name, &Dog::name>;

...そしてインスタンスメソッド;

struct Dog_bark { typedef string (Dog::*type)(); };
template class private_method<Dog_bark, &Dog::bark>;

すべての静的インスタンスメンバーで同じことを行う

struct Dog_Species { typedef string *type; };
template class private_member<Dog_Species, &Dog::Species>;

...そして静的インスタンスメソッド。

struct Dog_Legs { typedef int (*type)(); };
template class private_method<Dog_Legs, &Dog::Legs>;

今、あなたはそれらすべてをテストすることができます:

#include <assert.h>

int main()
{
    string name = "Fido";
    Dog fido = Dog(name);

    string fido_name = fido.*member<Dog_name>::value;
    assert (fido_name == name);

    string fido_bark = (&fido->*func<Dog_bark>::ptr)();
    string bark = "Fido: Woof!";
    assert( fido_bark == bark);

    string fido_species = *member<Dog_Species>::value;
    string species = "Canis familiaris";
    assert(fido_species == species);

    int fido_legs = (*func<Dog_Legs>::ptr)();
    int legs = 4;
    assert(fido_legs == legs);

    printf("all assertions passed\n");
};

出力:

$ ./main
all assertions passed

test_dog.cpp および dog.hpp のソースを確認できます。

[〜#〜]免責事項[〜#〜]:他の洞察のおかげで 賢い人 、私はアクセスできる前述の「ライブラリ」を集めました定義や動作を変更せずに、特定のC++クラスのプライベートメンバーおよびメソッドに。それを機能させるためには、(明らかに)クラスの実装を知り、含める必要があります。

[〜#〜]注[〜#〜]:私はこの回答の内容を改訂して、レビュアーが提案した指示に従うようにしました。

2
altamic

VariableImplを単体テストし、その動作が保証されている場合はVariableもそうするようにします。

内部のテストは世界で最悪のことではありませんが、インターフェイスコントラクトが保証されている限り、内部は何でもかまいません。それがVariableをテストするための奇妙なモック実装の束を作成することを意味する場合、それは合理的です。

それがたくさんあるように思われる場合は、実装の継承によって問題が大きく分離されないことを考慮してください。単体テストが難しい場合、それは私にとってかなり明白なコード臭です。

1
Tom Kerr

私は通常、クラスのパブリックインターフェイスをテストすることをお勧めしますnotプライベート/保護された実装。この場合、外部からパブリックメソッドで観察できない場合は、単体テストでテストする必要がない場合があります。

機能が子クラスを必要とする場合、いずれかのユニットテストで実際の派生クラスOR適切な実装を持つ独自のテスト派生クラスを作成します。

1
Mark B

Googleテストフレームワークの例:

// foo.h
#include "gtest/gtest_prod.h"
class Foo {
  ...
 private:
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  int Bar(void* x);
};

// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
  Foo foo;
  EXPECT_EQ(0, foo.Bar(NULL));
  // Uses Foo's private member Bar().
}

主なアイデアは、friendcppキーワードの使用です。この例は、次のように拡張できます。

// foo.h
#ifdef TEST_FOO
#include "gtest/gtest_prod.h"
#endif

class Foo {
  ...
 private:
  #ifdef TEST_FOO
  FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
  #endif
  int Bar(void* x);
};

TEST_FOOプリプロセッサーは、次の2つの方法で定義できます。

1)CMakeLists.txt内

option(TEST "Run test ?" ON)
if (TEST)
  add_definitions(-DTEST_FOO)
endif()

2)コンパイラの引数として

g++ -D TEST $your_args
0
user1823890