Unit testing c++. How to test private members?
我想为我的C++应用程序做单元测试。
测试类的私有成员的正确形式是什么?创建一个朋友类来测试私有成员、使用派生类或其他技巧?
测试API使用哪种技术?
通常情况下,我们只测试问题注释中讨论的公共接口。
但是,有时测试私有或受保护的方法很有帮助。例如,实现可能有一些不平凡的复杂性,这些复杂性对用户是隐藏的,并且可以通过对非公共成员的访问更精确地进行测试。通常,最好找出消除这种复杂性的方法,或者找出如何公开相关部分,但并非总是如此。
允许单元测试访问非公共成员的一种方法是通过friend构造。
回答这个问题涉及到许多其他主题。除了清洁代码、TDD和其他方面的宗教信仰之外:好的。
有几种方法可以访问私人成员。在任何情况下,您都必须否决测试代码!这可以在解析C++的两级(预处理器和语言本身)上进行:好的。
将全部定义为公共好的。
通过使用预处理器,可以破坏封装。好的。
1 2 3 | #define private public #define protected public #define class struct |
缺点是,交付代码的类与测试中的类不同!第92.13章中的C++标准表示:好的。
The order of allocation of non-static data members with different
access control is unspecified.Ok.
这意味着编译器有权为测试重新排序成员变量和虚拟函数。如果没有发生缓冲区溢出,这不会损害类,但这意味着您不会在交付时测试相同的代码。这意味着,如果您访问对象的成员,而该对象是由代码初始化的,并且是用
朋友好的。
此方法需要更改测试类,以使其与测试类或测试函数友好。一些测试框架,如gtest(
1 2 3 4 5 | class X { private: friend class Test_X; }; |
它只为测试打开类,不打开世界,但是您必须修改交付的代码。在我看来,这是一件坏事,因为测试不应该更改被测试的代码。作为另一个缺点,它给其他类的传递代码通过将自己命名为测试类来入侵类的可能性(这也会损害C++标准的ODR规则)。好的。
声明受保护的私有对象并从测试的类派生好的。
不是非常优雅的方式,非常侵入,但也适用于:好的。
1 2 3 4 5 6 7 8 9 10 | class X { protected: int myPrivate; }; class Test_X: public X { // Now you can access the myPrivate member. }; |
宏的任何其他方式好的。
有效,但在标准一致性方面与第一种方法有相同的缺点。例如。:好的。
1 2 3 4 5 6 | class X { #ifndef UNITTEST private: #endif }; |
我认为最后两种方法都不能替代前两种方法,因为它们与前两种方法相比没有优势,但对测试代码的干扰更大。第一种方法是非常危险的,所以你可以使用友好的方法。好的。
一些关于从不测试私事的讨论。单元测试的一个好处是,您将很早地到达这一点,在这一点上您必须改进代码的设计。这有时也是单元测试的缺点之一。它有时会使对象方向比必须的更复杂。尤其是如果您按照规则以与真实世界对象相同的方式设计类。好的。
然后,您必须有时将代码更改为一些难看的东西,因为单元测试方法强制您这样做。在用于控制物理过程的复杂框架上工作就是一个例子。在那里,您希望将代码映射到物理过程,因为过程的某些部分通常已经非常复杂。该进程上的依赖项列表有时会非常长。这是一个可能的时刻,测试私有成员变得越来越好。你必须权衡每种方法的优缺点。好的。
课程有时会变得复杂!然后你必须决定分拆它们,或者按照它们的原样来对待它们。有时候第二个决定更有意义。归根结底,这总是一个你想要实现哪些目标的问题(例如完美的设计、快速的整合时间、低的开发成本…)。好的。
我的意见好的。
我访问私人成员的决策过程如下:好的。
我不喜欢友好的方法,因为它改变了被测试的代码,但是测试某些东西的风险,可能与交付的不一样(第一种方法可能),将不能证明更干净的代码是正确的。好的。
顺便说一句:只测试公共接口也是一件流畅的事情,因为在我的经验中,它和私有实现一样经常改变。因此,您没有优势来减少对公共成员的测试。好的。好啊。
我自己也没有找到一个黄金解决方案,但是如果您知道测试框架如何命名它的方法,您可以使用
在我要测试的代码(stylesheet.h)的头部,我有:
1 2 3 4 5 6 7 8 9 10 11 | #ifndef TEST_FRIENDS #define TEST_FRIENDS #endif class Stylesheet { TEST_FRIENDS; public: // ... private: // ... }; |
在测试中我有:
1 2 3 4 5 6 7 8 9 10 11 | #include <gtest/gtest.h> #define TEST_FRIENDS \ friend class StylesheetTest_ParseSingleClause_Test; \ friend class StylesheetTest_ParseMultipleClauses_Test; #include"stylesheet.h" TEST(StylesheetTest, ParseSingleClause) { // can use private members of class Stylesheet here. } |
如果添加访问私人成员的新测试,则总是添加一行新行来测试朋友。这种技术的好处在于,它在被测试的代码中是相当不可靠的,因为您只添加了一些定义,这些定义在不进行测试时没有任何效果。缺点是在测试中有点冗长。
现在就说说你为什么要这样做。当然,理想情况下,您有一些职责明确的小类,并且这些类具有易于测试的接口。然而,在实践中,这并不总是容易的。如果您正在编写库,那么
测试私有成员的愿望是一种设计的味道,通常意味着有一个类被困在你的类中,挣扎着离开。类的所有功能都应该可以通过其公共方法来执行;实际上不存在不能公开访问的功能。
有几种方法可以让您认识到您需要测试您的私有方法是否按照它们在tin上所说的进行。友元类是其中最糟糕的类;它们以一种表面上脆弱的方式将测试与被测试类的实现联系起来。依赖注入更好:使私有方法的依赖类属性成为测试可以提供的模拟版本,从而允许通过公共接口测试私有方法。最好的方法是提取一个类,它封装了私有方法作为公共接口的行为,然后像通常那样测试新类。
有关详细信息,请参阅clean code。
尽管有关于测试私有方法的适当性的评论,但是假设您真的需要……例如,在将遗留代码重构为更合适的代码之前,通常是这样。以下是我使用的模式:
1 2 3 4 5 6 7 8 | // In testable.hpp: #if defined UNIT_TESTING # define ACCESSIBLE_FROM_TESTS : public # define CONCRETE virtual #else # define ACCESSIBLE_FROM_TESTS # define CONCRETE #endif |
然后,在代码中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include"testable.hpp" class MyClass { ... private ACCESSIBLE_FROM_TESTS: int someTestablePrivateMethod(int param); private: // Stuff we don't want the unit tests to see... int someNonTestablePrivateMethod(); class Impl; boost::scoped_ptr<Impl> _impl; } |
它是否比定义测试朋友更好?它看起来不像其他选项那么冗长,并且在标题中清楚地显示了正在发生的事情。这两种解决方案都与安全性无关:如果您真的关心方法或成员,那么这些方法或成员需要隐藏在不透明的实现中,可能还有其他保护。
有时需要测试私有方法。测试可以通过向类中添加friend_测试来完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // Production code // prod.h #include"gtest/gtest_prod.h" ... class ProdCode { private: FRIEND_TEST(ProdTest, IsFooReturnZero); int Foo(void* x); }; //Test.cpp // TestCode ... TEST(ProdTest, IsFooReturnZero) { ProdCode ProdObj; EXPECT_EQ(0, ProdObj.Foo(NULL)); //Testing private member function Foo() } |
在C++中使用一个简单的定义来解决问题。只需将"ClassUnderest"的内容包装如下:
1 2 3 4 5 | #define protected public #define private public #include <ClassUnderTest.hpp> #undef protected #undef private |
[信贷转到这篇文章和Ronfox][1]
我希望在单元测试的makefile中添加-dprivate=public选项,避免修改原始项目中的任何内容。