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)
我能看到的唯一方法是添加一个getter(例如,
我该怎么办?
如果要测试私有方法而不是私有变量,我该怎么做?
蒂亚
JIR
好吧,单元测试应该测试单元,理想情况下,每个类都是一个独立的单元——这直接遵循单一责任原则。
所以测试一个类的私有成员是不必要的——这个类是一个黑盒,可以像现在一样在单元测试中被覆盖。
另一方面,这并不总是正确的,有时也有充分的理由(例如,类的几个方法可能依赖于应该测试的私有实用函数)。一个非常简单、非常简单但最终成功的解决方案是将以下内容放入单元测试文件中,然后包括定义类的头文件:
1 | #define private public |
当然,这会破坏封装,而且是邪恶的。但是对于测试来说,它是有意义的。
对于受保护的方法/变量,从类继承一个测试类并进行测试。
对于私人,介绍一个朋友班。这不是最好的解决方案,但可以为您完成工作。
或者这个黑客
1 | #define private public |
一般来说,我同意其他人在这里所说的——只有公共接口才应该进行单元测试。然而,我刚刚遇到了一个案例,我必须首先调用一个受保护的方法,以准备一个特定的测试案例。我首先尝试了上面提到的
为此,我切换到基于
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 |
我通常建议测试类的公共接口,而不是私有/受保护的实现。在这种情况下,如果不能通过公共方法从外部世界观察到它,那么单元测试可能不需要测试它。
如果功能需要子类,可以单元测试真正的派生类,也可以创建自己的具有适当实现的测试派生类。
单元测试变量的作用是,如果保证了它的行为,那么变量也是如此。
测试内部不是世界上最糟糕的事情,但是目标是只要保证接口契约,它们就可以是任何东西。如果这意味着要创建一组奇怪的模拟实现来测试变量,那么这是合理的。
如果这看起来很像,那么考虑实现继承不会造成很大的关注点分离。如果很难进行单元测试,那么这对我来说是一种非常明显的代码味道。