我正在写一些单元测试。特别是我想测试一些私有方法。
到目前为止,我已经想出了使用。
但我对此并不满意,因为从单元测试的角度来看,它会破坏所有的封装。
您使用什么方法对私有方法进行单元测试。
- 您使用的是什么样的C++单元测试框架?
- 我用的是助推测试
- 请注意,在C++中有使用私有成员的方法。你可以在我的博客上读到:bloglitb.blogspot.com/2010/07/…
- #define private public—定义保留字是非法的。
- 您可以使用诸如isolator++之类的商业产品来伪造私有和受保护的方法。
- 这不是副本。这个问题是关于C++(它有EDCOX1×1),而另一个问题是非特异性的。它的最高答案是Java,建议反思。
与您在问题中提到的讨厌的#define黑客攻击不同,更干净的机制是让测试成为被测试类的朋友。这允许测试代码(和测试代码)访问私有,同时保护他们不受其他任何影响。
但是,最好通过公共接口进行测试。如果您的类X在私有成员函数中有很多代码,那么它可能值得提取一个新的类Y,该类Y由类X的实现使用。然后可以通过它的公共接口测试这个新的类Y,而不必向类X的客户机公开它的使用。
- 应该做的是使用编译器定义单元测试和ifdef单元测试朋友void unittestfcn()endif
- 是的,你可以有条件地让考试成为朋友。最好还是通过公共接口进行测试。
- 因此,您可以以某种方式公开这些方法:(
- 是的,测试公共方法,但是更改它们在哪个类上是公共的。如果新的类Y被命名为lib_namespace::detail::X_private_impl,那么它不太可能是X的临时用户。
- "如果您的类X在私有成员函数中有很多代码,那么提取一个新的类Y可能是值得的,该类被类X的实现所使用。"这样做的代价是增加名称空间和理解代码的成本。
如果您使用的是Google测试,那么您可以使用friend_测试轻松地将您的测试夹具声明为被测试类的朋友。
你知道,如果测试私有函数像其他一些答案所说的那样毫无疑问是糟糕的,那么它可能不会被内置到谷歌测试中。
在这个答案中,您可以阅读更多关于测试私有函数是好还是坏的信息。
- 这显然是向当局上诉的一个例子,但是…谷歌测试是多么权威啊!最佳答案。
- 你链接到的谷歌测试文档强烈建议重新设计测试私有方法。
- 是的,考虑重新设计(参见我在帖子中链接到的答案),但是"如果你必须测试非公共接口代码,你可以。"
如果这些方法足够复杂,可以单独进行测试,那么将它们重构为自己的类,并通过它们的公共接口进行测试。然后在原始类中私下使用它们。
- 是的,对单元测试私有方法的渴望通常是一种设计味道。
- 呵呵?为什么公共职能部门要比私人职能部门更需要进行更多的测试?
- 我不明白为什么想要单元测试一个私有方法是设计的味道。比如说,我开始编写一个类,同时编写单元测试,在开始编写公共函数之前,我编写了一些非常简单的私有函数,我想在开始实现公共函数之前证明它是有效的。
- @阿萨夫:不能随意测试私有函数,所以如果它们需要单独测试,您必须将它们公开(在它们所在的位置,或者在另一个类中),或者添加一个侵入性测试机制。我的建议是重构,以便所有代码都可以不受干扰地进行测试;在我看来(不是每个人都会共享),这还通过减少原始类的责任,并允许重用以前的私有功能,提供了一个更清洁的设计。如果你喜欢有多重责任的大型课程,可以使用安东尼的朋友测试课程建议。
- @大卫:这是一种味道,因为它表明类有多个职责(实现其公共接口,还实现私有功能)。将私有功能委托给其他类(而不是私有方法)可能是一种更清洁的设计。
- @公共函数需要更多的测试,因为它们是类接口的一部分。私有函数不是。您可以通过调用它们的函数间接测试私有函数(或其他外部无法访问的内部组件,例如某些公共函数的第4行),或者使用朋友,或者定义它们的正确操作并通过公共接口测试。我更喜欢1和3而不是2,所以如果有什么东西需要直接测试,我希望它是公开的(在某个地方)。作为该政策的直接后果,非公开的事物不需要直接测试。
- 如果确实提取了一个类以进行测试,请务必仔细考虑应该包括哪些方法以及如何命名该类。正确地完成后,您不仅使类更容易测试,而且还使维护工程师更容易理解,因为维护工程师最终会继承您的代码。
- @阿萨夫:从另一个角度来看,我认为如果在不更改类的公共接口的情况下更改类的实现细节(或者,如果您愿意,可以保护测试),则不需要删除测试。当您进行白盒测试时,您可能希望在重写后添加测试,以捕获新的"风险"案例。您不必删除它们——新的实现应该完成旧的实现所做的每一件重要的事情。但是私有功能是实现细节。当一个实现细节改变时停止工作的测试,即使实际上没有任何东西被破坏,也是干扰。
- @大卫:在你的特殊情况下,我会称之为即席程序员测试。编写测试作为类的一部分(可能是返回true或false的公共test_internals()函数)。可能是一个测试类,它是您类中的friend,或者使函数暂时公开。测试您的私人功能。然后编写公共函数,确保它们通过所有测试。一旦它们完成了,您就不再需要或希望您的测试来确保您的类包含特定的实现细节,所以不要签入这些测试。
- 理想情况下,您应该对所有函数进行单元测试…(alahaskell的quickcheck)如果您从未对私有方法进行单元测试,那么您如何相信它们在您自己的类中是正确的呢?
- 我同意@gnzlbg。所有功能都应该进行单元测试。您应该在开发函数时进行单元测试。还应该测试类的公共接口。但是,在抽象中移动得越远,可以进行的测试就越少。如果你只在公共层面进行测试,那么你的测试就没有什么意义了。设想一个类w/私有方法和(a,b)。最初实现正确,您的公共XFace测试通过。后来有人以XFace测试仍然通过的方式破坏了sum()。所以,你很高兴能继续运送一辆马车。单元测试所有功能-墨菲找到我们所有人!
- 所以,与其编写一百万个公共接口单元测试来尽可能地使用它背后的一切,不如为每个函数编写一个更小的测试,为公共接口编写一个更小的测试?你能想象一下,为了让公共接口测试所有可能的输入排列和方法调用,以获取特定私有方法的参数以获取某个值,你需要跳过的那些环吗?更容易测试方法本身(也更容易纠正)。
- @我的意思是:"更容易测试方法本身"——事实上,这正是我所建议的。采用(当前不稳定的)私有方法;将其移出原始类,使其成为公共的(因此是可测试的);将原始类重构为(私有的)使用(现在是公共的,并且是可测试的)函数。我当然不是建议尝试通过原始公共接口间接测试私有方法——正如您所说,这太疯狂了。
- 嗯,如果这个私有方法需要访问私有类字段(在被重构并移出原始类之后),您将如何处理这个问题?添加条件定义给友元单元测试类/函数似乎比友元许多公共函数更简单。
- @iheanyi:将私有方法以及它所依赖的任何内部内容重构为一个单独的类,如答案所示。希望这将通过将原始类与那些东西分离来简化它——尽管如果它仍然需要访问,那么新类将需要公开它。不需要交朋友,也不需要有任何条件。但这并不是一个扩展讨论重构技术的地方。
- 嗯,是的,这行得通。您需要结交一个类的唯一地方是实现密钥或代理客户机的类(或者与提供细粒度访问控制相关的类)。
- 你真的不需要一个单独的类来为很多人(大多数?)完成这个任务。应用。只需将实际实现移入tu局部函数(静态或未命名的命名空间)并为成员变量添加ref参数,然后从类和测试用例中调用该方法。没有朋友、重新定义或新的类为维护人员提供引用透明性,而且或多或少仍遵循被测试类型的接口。
- 对于某些情况,解决方案是用公共成员函数定义一个类并测试该类。然后,您可以在类中使用函数将类定义为私有数据成员,否则将具有私有成员函数。在该类中,必须使用成员函数作为数据成员来调用类。如base_class.class_with_functions.function()。在某些情况下,如果要作为数据成员包含的类具有静态变量,这可能不起作用。
使测试类成为原始类的朋友。此友元声明将位于#define UNIT_TEST标志内。
1 2 3 4 5
| class To_test_class {
#ifdef UNIT_TEST
friend test_class;
#endif
} |
现在,对于单元测试,您将使用标志-DUNIT_TEST编译代码。这样您就可以测试私有函数了。
现在,您的单元测试代码不会被推送到生产环境中,因为UNIT_TEST标志将是错误的。因此,代码仍然是安全的。
此外,您不需要任何特殊的库来进行单元测试。
- 你也可以把朋友放在一个#ifdef DEBUG里,这样你就不需要额外的旗帜了。
- 不需要#ifdef和#define部件。
- 更具体地说,(1)如果test_class已被转发声明,即使没有主体,也不需要#ifdef;和(2)如果编写friend class ::test_class,则不需要#ifdef(假设test_class在全局命名空间中,如果不可以使用namespace_name::test_class,只要声明了命名空间,或者如果test_class,则只需friend class test_class。]位于同一命名空间中。)
我知道这是一个古老的问题,但似乎没有人分享过我喜欢的相对好的方法,所以这里是:
将您希望测试的方法从private更改为protected。对于其他类,方法仍然是private,但是现在您可以从您的基类派生一个"测试"类,它公开了您想要测试的私有功能。
下面是一个最小的例子:
1 2 3 4 5 6 7 8 9 10 11
| class BASE_CLASS {
protected:
int your_method(int a, int b);
};
class TEST_CLASS : public BASE_CLASS {
public:
int your_method(int a, int b) {
return BASE_CLASS::your_method(a, b);
}
} |
当然,您必须更新单元测试才能在派生类而不是基类上运行测试,但是在这之后,对基类所做的任何更改都将自动反映在"测试"类中。
- 我刚刚得出结论,这是做测试的唯一方法。但这样做有点奇怪,因为当我想到访问说明符时,我想到的是对生产代码的访问,而不是对代码的测试。理想情况下,如果有关键字"testally-protected"和"testally-private"之类的话,我会喜欢的。
- 将private更改为protected将修改类的封装。如果你让你的测试夹具成为一个朋友,它可以获得它所需要的访问权限,但是其他的没有。
定义黑客是一个可怕的想法。当你去编译代码时,用预处理器随意地重新编写代码是不明智的。
现在正如一些人已经提到的,您是否应该测试私有方法还存在争议。但这并不包括故意隐藏构造函数以将Instantiaton限制在特定范围内的情况,或者其他一些更为深奥的情况。
而且,你不能在命名空间中建立朋友关系,而"友谊"不是在C++中继承的,所以取决于你的单元测试框架,你可能会遇到麻烦。幸运的是,如果您使用的是boost.test,那么这个问题的优雅解决方案是以fixture的形式出现的。
http://www.boost.org/doc/libs/1__0/libs/test/doc/html/utf/user-guide/fixture/per-test-case.html
您可以使这个fixture成为朋友,并让它实例化您在单元测试函数中使用的所有实例,将它们声明为fixture的静态实例和模块范围。如果您使用的是名称空间,不用担心,您可以只声明名称空间内的fixture和名称空间外的测试用例,然后使用scope resolution操作符来获取静态成员。
BOOST_FIXTURE_TEST_CASE宏将负责为您实例化和分解夹具。
很多小时后,我决定这是对那些想要测试其私有功能的人最好的解决方案。这是Max Deliso和Milo的答案组合?.
如果您使用的是Boost::Unit测试,那么有一个简单而优雅的解决方案。
在课堂上使用protected而不是private
1 2 3 4 5 6 7 8 9
| /* MyClass.hpp */
class MyClass {
protected:
int test() {
return 1;
}
}; |
创建设备:
1 2 3 4 5 6 7 8 9 10 11 12 13
| /* TestMyClass.cpp */
class F : public MyClass {};
BOOST_FIXTURE_TEST_SUITE(SomeTests, F)
// use any protected methods inside your tests
BOOST_AUTO_TEST_CASE(init_test)
{
BOOST_CHECK_EQUAL( test(), 1 );
}
BOOST_AUTO_TEST_SUITE_END() |
这样,您就可以自由地使用任何MyClass功能,而无需#define private public或向您的班级添加朋友!
- 这是迄今为止最好的解决方案。如果可以的话,我会投10次反对票。谢谢!
- 它不适用于私人。让私有保护仅用于单元测试似乎是荒谬的。
- @窃听,同意。而且,这比定义更糟糕,因为至少可以删除定义,而对于这个解决方案,您必须更改整个代码。范围永远不应该取决于您是否在测试代码。
我认为私有方法不需要单元测试用例。
如果方法是私有的,则只能在该类中使用。如果您已经使用这个私有方法测试了所有的公共方法,那么就不需要单独测试它,因为它只在这些许多方面使用。
- 不。假设您有一个带有公共接口和私有方法sum()的类。你写下来,你的公共接口单元测试都通过了。几个月过去了,有人在不破坏公共接口单元测试的情况下对sum进行了更改,从而引入了一个bug。公共接口是远离sum()的抽象级别。你越是抽象,就越难完全测试。仅仅因为公共接口似乎可以工作并不意味着其中存在你不想捕获的错误。
- 所以,与其编写一百万个公共接口单元测试来尽可能地使用它背后的一切,不如为每个函数编写一个更小的测试,为公共接口编写一个更小的测试?
- @我是说,如果使用sum()的公共方法的测试不随着变化而中断,为什么您希望sum()本身的单元测试中断?
- @Kylestrand轻松。假设sum()执行以下操作:temp=x+a;result=a+y。编写一个单元测试来验证temp和结果的计算是否正确。我将sum()改为:temp=y+a;result=a+x;单元测试中断,公共接口仍然工作。这是一个很普通的例子,但它显示了当一个单元测试与私有实现紧密耦合时,它是多么的脆弱。
- @公共接口是一种契约。你可以合理地确定它不会改变。私有接口中没有任何保证。它可以发生巨大的变化,仍然可以实现公共接口所承诺的完全相同的行为。编写耦合到私有方法/变量的单元测试是无用的。每次私有内容发生变化时,您都需要对它们进行更改,因此您的测试只反映系统的当前行为。它们不会让你引入错误,因为你修改了它们,使之与错误代码兼容。
- @Kylestrand如果单元测试与私有实现耦合,那么每次对私有端进行更改时,都必须更改单元测试。你的单元测试只是两次编写相同代码的练习,你已经破坏了单元测试的目的。单元测试应该确保公共接口承诺的行为实际有效,并且应该通过重构私有实现来捕获引入的错误。如果您的单元测试依赖于私有实现,那么它们就不能捕获这样的错误,并且实际上也不会测试公共接口。
- @我有点困惑。您最初的评论,我是回应似乎是说,您确实需要单元测试的私人功能("只是因为公共接口似乎工作…")。但是您的声明"编写与私有方法/变量耦合的单元测试是无用的",以及您对我的其余评论,似乎表明您实际上同意我的观点,即针对私有函数sum()的单元测试不一定有助于捕获错误。
- @Kylestrand是的,重读我最初的评论,它似乎是在我更好地理解单元测试之前写的。然后,一年后看到你的评论,我错误地将其解释为与我目前的观点相矛盾,没有意识到(或阅读发现)我最初的评论也与我目前的观点相矛盾!很抱歉弄混了。这并不是每天都会遇到这种过去误解的具体证据。)
- @我是说,嗯,那就到处喝吧!