• 2007-09-17

    闲聊C++单元测试框架

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/8315523.html

    今天下午,和Darwin聊了一下C++单元测试框架,主要参考对象是CppUnit和CxxTest。

    表现形式
    因为C++不支持reflection,所以,必须要做一些额外的工作,让框架知道相关内容的存在。CppUnit的做法是用宏进行注册。这种做法要求我们每添加一个测试,就要考虑用相应的宏进行注册,这种做法很繁琐,最大的问题在于由于疏忽而遗漏,这种靠人工保证的东西不可靠。在这点上,CxxTest做得要好一些,有一个专门的脚本做这件事。通过这个脚本扫描这个自己编写的文件,生成一些新的文件,完成这个工作。从代码的表现力和可靠度来说,要好得多。唯一的问题是引入了一个脚本,而且这个脚本一般是由某些动态语言写成的(目前的CxxTest有Perl和Python的脚本),从而引入了对这种语言的依赖。不过,由于C++语言本身的限制,从接口的角度来看,这种做法已经很不错了。

    语法
    有一种C++的单元测试框架叫TUT,Template Unit Test的缩写。顾名思义,它是用模板完成的(其实,CppUnit和CxxTest都有模板的部分)。随着C++编译器的进步,在大多数情况下,模板都是可以顺利通过编译的。但是,不要忘了,还有一种环境叫嵌入式,那里的编译器基本上还是很原始的,模板并不见得能够顺利的通过编译。

    此外,模板还会带来另外一个问题,编译时间的增长,相信有过模板编程经验的人都会对此深有体会。编译时间增长意味着什么?我们接下来讨论。

    编译时间
    有一种敏捷实践叫做测试驱动开发(Test Driven Development)。测试驱动开发的基础是单元测试。测试驱动开发希望达成的一个目标是快速反馈,所以,站在C++语言的角度,如果执行时间受限于代码本身无法缩短,那么我们希望编译时间尽可能短,这样,才不会把生命都浪费在等待代码编译上。

    除了刚才提到的模板问题之外,CppUnit会把所有测试编译生成一个可执行文件,这意味着什么?几乎修改任何一个文件都会造成这个文件的重新生成。随着目标文件的增加,这个过程时间就会增长。相对于修改范围(可能只是某一个文件),还是显得有些长了。为什么Java语言不会存在这种现象?因为Java是动态连接的,所以,Java生成.class就结束了。对应到C++上,这只是完成了目标文件的生成,而在C++我们不得不再进一步生成可执行文件。从道理上,CxxTest可以为不同的测试文件生成不同的可执行文件,不过这么做又少了总体的过程,统计起来又显得心有余力不足了,而且通常不会这么做。

    个人而言,对这几个单元测试框架都不是非常了解,如果前面的讨论存在谬误,欢迎有识之士指出。
    分享到:
    引用地址:

    评论

  • 你的文章很有意思,偶喜欢Agille,但没在项目中真正实践过,能认识你下,以后向你请教吗?
    回复拥抱变化说:
    谈不上请教,大家共同学习进步!
    欢迎与我联系!
    2007-09-20 10:49:32
  • 一直用cppunit.还有不少其他的选择,如boost unit test以及更不知名的,但至少cppunit符合使用习惯.



    比较麻烦的地方你也提到了,就是不能run一个单一的测试,要多写好几行的macro.



    如果严格执行Test Driven Development,先写一份fail的测试,会避免"由于疏忽而遗漏"而遗漏的问题,但编译和必须运行全套测试的限制,也不是很方便坚持这点.

    回复yawl说:
    你说得对,如果TDD的话,应该可以避免遗漏。只是,这是行为方式带给我们的,并不是框架本身带来的。
    2007-09-18 10:30:54