如何对C ++类的私有成员(和方法)进行单元测试

How to do unit testing on private members (and methods) of C++ classes

本问题已经有最佳答案,请猛点这里访问。

我对单元测试很陌生,有点困惑。

我试图在一个叫做EDCOX1(0)的C++类上进行单元测试(使用升压单元测试框架)。这是详细情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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调用另一个类的方法,然后清除向量。

我能看到的唯一方法是添加一个getter(例如,GetMean),但我根本不喜欢这个解决方案,也不认为它是最优雅的。

我该怎么办?

如果要测试私有方法而不是私有变量,我该怎么做?

蒂亚

JIR


好吧,单元测试应该测试单元,理想情况下,每个类都是一个独立的单元——这直接遵循单一责任原则。

所以测试一个类的私有成员是不必要的——这个类是一个黑盒,可以像现在一样在单元测试中被覆盖。

另一方面,这并不总是正确的,有时也有充分的理由(例如,类的几个方法可能依赖于应该测试的私有实用函数)。一个非常简单、非常简单但最终成功的解决方案是将以下内容放入单元测试文件中,然后包括定义类的头文件:

1
#define private public

当然,这会破坏封装,而且是邪恶的。但是对于测试来说,它是有意义的。


对于受保护的方法/变量,从类继承一个测试类并进行测试。

对于私人,介绍一个朋友班。这不是最好的解决方案,但可以为您完成工作。

或者这个黑客

1
#define private public


一般来说,我同意其他人在这里所说的——只有公共接口才应该进行单元测试。然而,我刚刚遇到了一个案例,我必须首先调用一个受保护的方法,以准备一个特定的测试案例。我首先尝试了上面提到的#define protected public方法;这在Linux/GCC中有效,但在Windows/VisualStudio中失败。原因是把protected改为public也改变了被破坏的符号名,从而给了我链接器错误:库提供了一个受保护的__declspec(dllexport) void Foo::bar()方法,但是在#define就位的情况下,我的测试程序期望有一个公共的__declspec(dllimport) void Foo::bar()方法,这给了我一个未解决的符号错误。

为此,我切换到基于friend的解决方案,在我的类标题中执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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;
};

在我的实际测试案例中,我这样做了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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。


测试C++中受保护数据的好方法是朋友代理类的分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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-翻译/


在我看来,测试一个类的私有成员/方法的需要是代码嗅觉,我认为C++中技术上是可行的。

例如,假设您有一个包含私有成员/方法(公共构造函数除外)的Dog类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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.h的头以及所需的实现,如:

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

然后根据任何实例成员的类型映射一些存根

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

…和实例方法;

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

对所有静态实例成员执行相同的操作

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

…和静态实例方法。

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

现在,您可以对它们进行全部测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include

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
"
);
};

输出:

1
2
$ ./main
all assertions passed

您可以查看test_dog.cpp和dog.hpp的来源。

免责声明:由于其他聪明人的洞察力,我已经组装了上述的"库",能够访问给定的C++类的私有成员和方法,而不改变其定义或行为。为了使它工作,(显然)需要知道并包括类的实现。

注意:我修改了这个答案的内容,以便遵循评审员建议的指示。


来自Google测试框架的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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().
}

主要的想法是使用friend cpp关键字。您可以将此示例扩展如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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);
};

您可以用两种方式定义测试foo预处理器:

1)在cmakelists.txt中

1
2
3
4
option(TEST"Run test ?" ON)
if (TEST)
  add_definitions(-DTEST_FOO)
endif()

2)作为编译器的参数

1
g++ -D TEST $your_args


我通常建议测试类的公共接口,而不是私有/受保护的实现。在这种情况下,如果不能通过公共方法从外部世界观察到它,那么单元测试可能不需要测试它。

如果功能需要子类,可以单元测试真正的派生类,也可以创建自己的具有适当实现的测试派生类。


单元测试变量的作用是,如果保证了它的行为,那么变量也是如此。

测试内部不是世界上最糟糕的事情,但是目标是只要保证接口契约,它们就可以是任何东西。如果这意味着要创建一组奇怪的模拟实现来测试变量,那么这是合理的。

如果这看起来很像,那么考虑实现继承不会造成很大的关注点分离。如果很难进行单元测试,那么这对我来说是一种非常明显的代码味道。