为什么有如此多的C++测试框架 - from Google Testing Blog
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++开发要求大量额外的精力:
- 你必须使用不同的编译器,摆弄不同的编译开关,生成代码在不同的操作系统上运行测试。
- 某些平台的特殊要求使得你必须使用条件编译。
- 不同的编译器可能存在bug,你必须小心的修改代码绕来绕去。
- 除非你在一台裸机上工作,不然这一切太让人崩溃了。
所以,我的结论是:为什么我们有如此多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的更多相关文章
- Google+团队如何测试移动应用 - from Google Testing Blog
How the Google+ Team Tests Mobile Apps by Eduardo Bravo Ortiz “移动第一”在当下已成为很多公司的口头禅.但是能够用一种合理的方法来测试移动 ...
- 与谷歌测试工程师的对话 - from Google Testing Blog
Conversation with a Test Engineer by Alan Faulner Alan Faulner谷歌的一名测试工程师,他工作在DoubleClick Bid Manager ...
- Google C++测试框架系列:入门
Google C++测试框架系列:入门 原始链接:V1_6_Primer 注 GTest或者Google Test: Google的C++测试框架. Test Fixtures: 这个词实在找不到对应 ...
- 用 Python 测试框架简化测试
用 Python 测试框架简化测试 摘要:本文将向您介绍了三种流行 Python 测试框架(zope.testing,py.test,nose)的基本特性,并讨论新一代的测试风格. 最近出现了行业级的 ...
- phpunit 测试框架安装
PHPUnit是一个轻量级的PHP测试框架.它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计).来自百度百科 一.下载wg ...
- 某互联网后台自动化组合测试框架RF+Sikuli+Python脚本
某互联网后台自动化组合测试框架RF+Sikuli+Python脚本 http://www.jianshu.com/p/b3e204c8651a 字数949 阅读323 评论1 喜欢0 一.**Robo ...
- selenium测试框架使用xml作为对象库
之前已经写过一篇: selenium测试框架篇,页面对象和元素对象的管理 上次使用的excel作为Locator对象管理,由于excel处理不够方便,有以下缺点: 不能实现分page 加载Locato ...
- selenium 测试框架中使用grid
之前的测试框架:http://www.cnblogs.com/tobecrazy/p/4553444.html 配合Jenkins可持续集成:http://www.cnblogs.com/tobecr ...
- selenium测试框架篇,页面对象和元素对象的管理
前期已经做好使用Jenkins做buildhttp://www.cnblogs.com/tobecrazy/p/4529399.html 做自动化框架,不可避免的就是对象库. 有一个好的对象库,可以让 ...
随机推荐
- Redmine迁移至华为软件开发云-项目管理
一.方案概述 要想将Redmine中某个项目的数据导入到华为软件开发云(以下简称开发云),如果说是按照Redmine中的数据一条一条的在开发云中新建出来,肯定不是一个明智的方案,下面就是给大家介绍一个 ...
- SQL Server 存储过程之嵌套游标
下面是一个订单取消的含2个游标的存储过程 set ANSI_NULLS ON set QUOTED_IDENTIFIER ON go ALTER PROCEDURE [dbo].[CancelOrde ...
- Java 9 特性
Java 8 发布三年多之后,已经于在2017年9月21日发布了. 你可能已经听说过 Java 9 的模块系统,但是这个新版本还有许多其它的更新. 这里有九个令人兴奋的新功能. 1. Java 平台级 ...
- QT环境安装VS2013(系统环境里设置QTDIR,并添加VS2013的BIN目录)
QT环境安装VS2013 文件名 1.qt5.5.1 qt-opensource-windows-x86-msvc2013-5.5.1.exe 2.qt vs插件 3.qt-vs-addin-1.2. ...
- 微信公众平台通用接口API指南
微信公众平台 通用接口 消息接口 开发模式 作者:方倍工作室原文:http://www.doucube.com/index.php?m=Article&a=show&id=5 微信公众 ...
- [UWP]使用Writeable?Bitmap创建HSV色轮
原文:[UWP]使用Writeable?Bitmap创建HSV色轮 1. HSV 1.1 HSV的定义 HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法,这种表示法试图做到比RGB基于笛卡尔 ...
- 新版本MenuDemo——使用Duilib模拟Windows本机菜单
相信玩Duilib朋友已经开始期待一个很长的文章.由于我的文章在一周前公布--"无焦点窗体的实现"里面提到了无焦点窗体在菜单里面的应用,并承诺大家,写一个关于Menu实现的Demo ...
- 阿里将成为下一个谷歌?是谁Google真正的挑战者
非常多观点觉得阿里下一步即将成为google的挑战者,但不管从技术储备还是产业布局来看,阿里都难当此任.在产业模式上.电商挑战搜索尚有诸多短板,在解决这些根本问题前,空泛谈论这些是没有意义的. < ...
- MySQL 执行原生sql
public class MySqlHelper { private YourContext _context; public MySqlHelper(YourContext context) { _ ...
- Delphi 接口使用中,对象生命周期管理,如何释放需要注意的问题
网上有篇文章<Delphi接口编程的两大陷阱>,里面提到接口的生存期管理的问题.但该文章里面提到的两个问题,其实都是对 Delphi 不理解导致的. 先说该篇文章中提到的第一个问题为什 ...