Why Are There So Many C++ Testing Frameworks? by Zhanyong Wan (Software Engineer)

最近貌似有很多人正在开发他们自己的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在默认情况下是不需要异常和动态类型识别的。但是当这些特性被打开时,Google Test会试着去利用它们给你提供一些新功能,比如基于异常的断言exception assertions。

为什么不写一个可移植的框架?是的,这是Google Test的最高设计目标,而且很多框架的设计者也已经努力过了。但是,自由不是无代价的。跨平台的C++开发要求大量额外的精力:

  1. 你必须使用不同的编译器,摆弄不同的编译开关,生成代码在不同的操作系统上运行测试。
  2. 某些平台的特殊要求使得你必须使用条件编译。
  3. 不同的编译器可能存在bug,你必须小心的修改代码绕来绕去。
  4. 除非你在一台裸机上工作,不然这一切太让人崩溃了。

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

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

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

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

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

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

 TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(, Factorial());
EXPECT_EQ(, Factorial());
EXPECT_EQ(, Factorial());
EXPECT_EQ(, Factorial());
}

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

 bool IsInRange(int value, int low, int high) {
return low <= value && value <= high;
} // ... EXPECT_TRUE(IsInRange(SomeFunction(), low, high));

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

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

 EXPECT_TRUE(IsInRange(SomeFunction(), low, high))
<< "SomeFunction() = " << SomeFunction()
<< ", not in range ["
<< 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的返回值怎么办?我们需要把这种模式抽象为一个可重用的结构。

Google Test允许你自定义类似于如下的测试断言:

 AssertionResult IsInRange(int value, int low, int high) {
if (value < low)
return AssertionFailure()
<< value << " < lower bound " << low;
else if (value > high)
return AssertionFailure()
<< value << " > upper bound " << high;
else
return AssertionSuccess()
<< value << " is in range ["
<< low << ", " << high << "]";
}

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

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

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

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

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

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

就像共产主义一定要实现一样,总有一天,C++测试框架碎片化的问题也会得到解决。

为什么有如此多的C++测试框架 - from Google Testing Blog的更多相关文章

  1. Google+团队如何测试移动应用 - from Google Testing Blog

    How the Google+ Team Tests Mobile Apps by Eduardo Bravo Ortiz “移动第一”在当下已成为很多公司的口头禅.但是能够用一种合理的方法来测试移动 ...

  2. 与谷歌测试工程师的对话 - from Google Testing Blog

    Conversation with a Test Engineer by Alan Faulner Alan Faulner谷歌的一名测试工程师,他工作在DoubleClick Bid Manager ...

  3. Google C++测试框架系列:入门

    Google C++测试框架系列:入门 原始链接:V1_6_Primer 注 GTest或者Google Test: Google的C++测试框架. Test Fixtures: 这个词实在找不到对应 ...

  4. 用 Python 测试框架简化测试

    用 Python 测试框架简化测试 摘要:本文将向您介绍了三种流行 Python 测试框架(zope.testing,py.test,nose)的基本特性,并讨论新一代的测试风格. 最近出现了行业级的 ...

  5. phpunit 测试框架安装

    PHPUnit是一个轻量级的PHP测试框架.它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计).来自百度百科 一.下载wg ...

  6. 某互联网后台自动化组合测试框架RF+Sikuli+Python脚本

    某互联网后台自动化组合测试框架RF+Sikuli+Python脚本 http://www.jianshu.com/p/b3e204c8651a 字数949 阅读323 评论1 喜欢0 一.**Robo ...

  7. selenium测试框架使用xml作为对象库

    之前已经写过一篇: selenium测试框架篇,页面对象和元素对象的管理 上次使用的excel作为Locator对象管理,由于excel处理不够方便,有以下缺点: 不能实现分page 加载Locato ...

  8. selenium 测试框架中使用grid

    之前的测试框架:http://www.cnblogs.com/tobecrazy/p/4553444.html 配合Jenkins可持续集成:http://www.cnblogs.com/tobecr ...

  9. selenium测试框架篇,页面对象和元素对象的管理

    前期已经做好使用Jenkins做buildhttp://www.cnblogs.com/tobecrazy/p/4529399.html 做自动化框架,不可避免的就是对象库. 有一个好的对象库,可以让 ...

随机推荐

  1. React为啥很多类里的标签上事件处理函数要用bind(this)

    render() { return ( <div> <p onClick={this.clickHandler.bind(this)}>vz</p> </di ...

  2. spring-boot-quartz, 依赖spring-boot-parent good

    /** * state的值代表该任务触发器的状态: STATE_BLOCKED 4 // 运行 STATE_COMPLETE 2 //完成那一刻,不过一般不用这个判断Job状态 STATE_ERROR ...

  3. C#实现通过拼多多分享微信公众号实现查询优惠券、佣金比率

    主要实现功能:关注公众号的用户发送拼多多商品链接,后台程序通过链接查找商品优惠券或返佣情况. 说明:使用了niltor 封装的拼多多接口 github地址 ,但是需要注意可能会存在返回模型无法正确解析 ...

  4. 在.net core不同的版本中 webabi引用的包不同

    core2.0中: 为了要使用MVC Controller 要安装 Microsoft.AspNetCore.Mvc.Core包 Core2.1中:Microsoft.AspNetCore.App

  5. Qt Installer Framework 3.0.1 Released(功能比较强)

    We are happy to announce the release of Qt IFW 3.0.1. 3.0.1 is fully compatible with 2.0.5, which me ...

  6. easyui的datebox最简单的方法来格式化

    看了网上有很多解决方案,我也写了一个比较简单的方法. 实现easyui的datebox格式化. 效果例如以下.用"++"隔开,看你喜欢用什么都能够. 1.html <span ...

  7. WPF版的HideCaret()

    原文:WPF版的HideCaret() WPF版的HideCaret() 周银辉 事情是这样的: 一般说来,对于那些拥有句柄的TextBox(RichTextBox同理)控件,比如win32的,Win ...

  8. HTTP协议学习 - 9 Method Definitions

    # 前言 官方文档简略翻译.9 不是代表第九篇,而是在 RFC2616 中是第九篇.重要加粗,龟速翻译. # Method 9.3 GET The GET method means retrieve ...

  9. swagger-editor

    前言 上一篇文章我们有提到Swagger做接口的定义是采用yaml语言的,当然,yaml是个啥,大家自行百度.阿福在此不做赘述了.但是,今天我们要来讲的是yaml支持比较好的Swagger-Edito ...

  10. 专门用于消息回调窗口的窗口标识HWND_MESSAGE(创建一个非可视、没有z-order的窗口)

    HWND_MESSAGE Message-Only Windows A message-only window enables you to send and receive messages. It ...