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. [ACM] POJ 1094 Sorting It All Out (拓扑排序)

    Sorting It All Out Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 26801   Accepted: 92 ...

  2. Android之assets资源目录的各种操作

    第一种方法:       String path = file:///android_asset/文件名; 第二种方法:         InputStream abpath = getClass() ...

  3. 【50.88%】【Codeforces round 382B】Urbanization

    time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  4. OpenCV For iOS 1:&#160;连接OpenCV 3.0

    本文的内容參考Instant OpenCV for iOS结合最新的开发平台完毕. 本系列文章採用的的开发环境为: 1)Xcode 6 2)OpenCV for iOS 3.0.0 alpha 接下来 ...

  5. python 两个链表的第一个公共结点

    题目描述 输入两个链表,找出它们的第一个公共结点.   看到这道题的时候,很多人的第一反应就是采用蛮力的方法:在第一个链表上顺序遍历每个节点,每遍历到一个节点的时候,在第二个链表上顺序遍历每个节点.如 ...

  6. 保存画面为图片 当前MFC保存该程序为图片 c++ vc

    将屏幕保存为图片.使用vs2008编译通过. [cpp] view plaincopy #include "stdafx.h" #include <windows.h> ...

  7. ude—基于udp的全双工可靠传输协议

    ude是一款基于udp的可靠传输协议,专门用于在数据传输方面对实时性要求较高的应用领域.    tcp协议虽然能保证数据的可靠传输,但它有以下几个缺点:1.tcp的数据确认机制会导致发送方重复发送一些 ...

  8. OpenGl(二)点线设置、多边形镂空

    1. 改变点的大小 OpenGL中默认点的大小是1个像素,使用函数glPointSIze可以调整点的大小,入参是GLfloat,相当于是浮点数. 相关代码: void myDisplay(void) ...

  9. ASP.NET Core macOS 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core macOS 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 是对 ASP.NET 有重大意义的一次重新设计.本章节我 ...

  10. mac 端安装JAVA开发环境

    一.maven安装 下载地址   https://maven.apache.org/download.cgi 下载 apache-maven-3.5.2-bin-zip 将下载的文件放在某路径下 修改 ...