分享

为什么有如此多的C++测试框架-From Google testing blog

 quasiceo 2012-12-28

为什么有如此多的C++测试框架-From Google testing blog

原文地址 ()

 

最近貌似有很多人在开发他们自己的C++测试框架(如果还没有实现完成的话^_^)。Wiki上有一个此类框架的不完全列表(注:一个巨长的表和功能矩阵。)。因为大多数面向对象编程语言只有1到2个主要的框架,对C++而言这就显得有趣了。例如,大多数Java开发者都使用JUnit或TestNG。难道C++程序员都是疯狂的DIY爱好者吗?

 

当我们开始开发,特别是把Google Test(Google 的C++测试框架)开源后,人们开始好奇为什么我们要做这件事。简单的回答是我们没有能够找到一个已有的并且能够满足需求的C++测试框架。这并不表明现 有的框架设计和实现的很糟糕,事实上我们也从中学到了很多伟大的想法和实现技巧。但是google有如此多的C++项目需要在不同的平台上用不同的编译器 编译,同时还有着无数的编译选项,我们确实需要有这么一个框架能够在这种环境下处理不同类型和规模的项目。

 

和有着著名口号“一次编程,到处运行”的Java不同,C++代码的编写在一个更复杂多变的环境下进行。由于语言本身的复杂性以及处理底层任务的需 求,不同的C++编译器甚至不同版本的兼容性之间存在着许多问题。虽然有着C++标准,但是到现在为止还没有一个编译器厂商能很好的全面支持。很多时候因 为任务需要,你必须使用一些不可移植的扩展或者平台相关的功能。以上这些原因使得编写一个能使用不同编译器在在不同平台上工作的复杂系统变得十分困难。

 

更头大的事还没完,大多数C++编译器允许你关闭一些标准语言特性来获取更好的性能。不喜欢异常?关了。不喜欢动态转换(Dynamic Cast)?把动态类型识别(RTTI) 禁用好了,虽然它能提供动态转换和运行时类型信息访问。如果你选择这么做,那么使用到以上特性的代码就会编译失败。许多测试框架都依赖异常(想想你把异常 关掉会发生什么?)。但对于我们来说这不是一个问题,因为我们的大多数项目都是禁用异常的(也许你会好奇,但是让我告诉你,Google Test在默认情况下是不需要异常和RTTI的。但是当这些特性被打开时,Google Test会试着去利用它们给你提供一些新功能,比如exception assertions。)。

 

为什么不写一个可移植的框架?是的,这是Google Test的最高设计目标(: 这个就是和不能实现一个意思。),而且很多框架的设计者也已经努力过了。但是,自由不是无代价的。跨平台的C++开发要求大量额外的精力:1)你必须使用 不同的编译器,摆弄不同的编译开关,生成代码在不同的操作系统上运行测试。2)某些平台的特殊要求使得你必须使用条件编译。3)不同的编译器可能存在 bug,你必须小心的修改代码绕来绕去。4)除非你在一台裸机上工作,不然这一切太让人崩溃了。

 

所以,我的结论是:为什么我们有如此多C++测试框架的原因是出在C++实现自身,不同环境之间的变化使得编写可移植的C++代码是如此困难。John的框架可能完美的解决了John的问题,但是对Bill却一点不适用。

 

另外一个原因我想是:C++自身的限制使得我们无法很好的实现一些功能,为了绕过一些限制大家只好各显神通了。 一个显著的例子就是C++是一个静态类型语言并且不提供反射(Reflection)机制。大多数Java测试框架可以使用反射自动找到你编写的测试用 例,这样你就不用一个个去手工注册了(很有可能你写了测试用例而忘记去注册,并且手工维持更新也是很痛苦的。)。因为C++没有反射,我们只能用不同的方 法。不幸的是没有一个完美的解决方案。一些框架要求你手工注册测试用例,而另一些框架使用脚本分析你的代码来找到测试用例,还有一些则利用宏来完成自动注 册(:微软的LTM就是这么做的。)我们认为最后一个使用宏的解决方案是最好的,并且对大多数人都有效。目前有好几种用宏来实现的方法,各有各的利弊,最终结论还是有待商榷的。

让我们看一些实际代码来理解Google Test是如何解决测试用例注册问题的。最简单的方法是使用TEST宏来添加一个测试用例。

1 TEST(Subject, HasCertainProperty) {
2     // … testing code goes here …
3 }

它定义了一个测试用例来检查某个Subject是否包含一些特定的属性(Property)。宏自动向Google Test注册这个测试用例,所以当测试程序执行时这个用例会被覆盖。

 

这里有个更加实际的用来检测阶乘函数在使用正整数时能正确工作的例子:

1 TEST(FactorialTest, HandlesPositiveInput) {
2   EXPECT_EQ(1, Factorial(1));
3   EXPECT_EQ(2, Factorial(2));
4   EXPECT_EQ(6, Factorial(3));
5   EXPECT_EQ(40320, Factorial(8));
6 }

最后,许多C++测试框架的作者都忽略了扩展性并且满足于提供一个打包了的解决方案:原文是canned solutions。),这导致的后果就是我们留下了很多解决方案,每个都只针对一些特定的问题而不足够通用。一个万能的框架必须提供足够的扩展能力。让我们来考虑一下:你无论如何不可能满足所有人的需求(: 原文是You cannot be all things to all people。)。我们可以提供一个一揽子解决方案满足95%的需求,而不是为了一些很少有人用的功能而塞入大量的臃肿的代码。我们可以开放接口来让用户 自己根据需要实现那些功能。如果我能轻松的扩展已有框架来实现我需要的特定功能,我就不会选择重起炉灶写一个新的框架。但是大多数框架的作者都没有看到可 扩展性的重要。我认为正是这种思路导致了今天这种百花齐放的局面。在Google Test的实现中,我们努力使得你可以通过自定义断言(:原文是defining custom assertions。)生成更有意义的错误信息来简单的扩展你的测试词汇表(:原文是testing vocabulary。)。这里有个简单的例子显示如何判断一个值是否在给定的范围:

复制代码
1 bool IsInRange(int value, int low, int high) {
2   return low <= value && value <= high;
3 }
4 
5 //...
6 
7 EXPECT_TRUE(IsInRange(SomeFunction(), low, high));
复制代码

当你的断言失败时,你只知道函数SomeFunction返回的值不在[low, high]的范围中,但是你不知道返回值和期望范围是多少,这就使得代码的调试变得比较麻烦了。

 

但是你可以提供自己定制的信息来提供断言失败时更有意义的描述:

1 EXPECT_TRUE(IsInRange(SomeFunction(), low, high))
2     << "SomeFunction() = " << SomeFunction() 
3     << ", not in range ["
4     << low << ", " << high << "]";

如果考虑到SomeFunction可能每次返回不同的值,我们可以把代码稍作修改:

int result = SomeFunction();
EXPECT_TRUE(IsInRange(result, low, high))
    << "result (return value of SomeFunction()) = " << result
    << ", not in range [" << low << ", " << high << "]";

上面的办法看着可行但是太麻烦了,假如你要调用几百次EXPECT_TRUE来检测SomeFunction的返回值怎么办?我们需要把这种模式抽象为一个可重用的结构(:原文是abstract this pattern into a resusable construct。)。

 

Google Test允许你自定义类似于如下的测试断言(:原文是test predicate。):

复制代码
 1 AssertionResult IsInRange(int value, int low, int high) {
 2   if (value < low)
 3     return AssertionFailure()
 4         << value << " < lower bound " << low;
 5   else if (value > high)
 6     return AssertionFailure()
 7         << value << " > upper bound " << high;
 8   else
 9     return AssertionSuccess()
10         << value << " is in range [" 
11         << low << ", " << high << "]";
12 }
复制代码

当范围是[20, 60]而SomeFunction返回13时如下信息将被打印:

Value of: IsInRange(SomeFunction(), low, high)
  Actual: false (13 < lower bound 20)
Expected: true

同样的IsInRange定义还可以被用于EXPECT_FALSE的情况,例如"EXPECT_FALSE(AnothFunction(), 20, 60)",AnotherFunction返回25,如下信息将被打印:

Value of: IsInRange(AnotherFunction(), low, high)
  Actual: true (25 is in range [20, 60])
Expected: false

通过以上方式,你可以为特定的问题领域建立一个断言库,从这些清晰的,陈述性的代码和有意义的错误信息中获益。

 

于此一脉相承的还有Google Mock(我们的C++ mocking框架)允许你自定义matchers和系统自带的matchers一起无差别的使用。我们还在Google Test引入了事件监听API使得用户可以自行编写插件。我们希望用户能够充分使用这些特性扩展Google Test/Mock以满足他们的需求,如果能提交一些好的扩展给我们那是最好不过的了。

 

就像共产主义一定要实现一样,总有一天,C++测试框架碎片化的问题也会得到解决(:还是先解决Android版本碎片化来得实在些。)。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多