Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2349152
  • 博文数量: 527
  • 博客积分: 10343
  • 博客等级: 上将
  • 技术积分: 5565
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-26 23:05
文章分类

全部博文(527)

文章存档

2014年(4)

2012年(13)

2011年(19)

2010年(91)

2009年(136)

2008年(142)

2007年(80)

2006年(29)

2005年(13)

我的朋友

分类: WINDOWS

2009-11-17 21:07:25

虽然有了Nunit, C#程序员仍然不愿做单元测试, 可能的一条原因是使用不便, 调用Nunit是有成本的, 你还需要考虑被测试的程序集的路径和对其它库的依赖关系.

TestDriven.NET 做了一件简单的做的极好的事: 让你的单元测试变成几乎是零成本的, 你基本不需要知道 Nunit,  把光标置于你的函数之上, 右键, 就可以测试, TestDriven.NET会:

Build 项目(如有必要)
根据光标所在的上下文, 运行一个函数, 或一个类, 或一个项目

当然最有用的情况还是需要与Nunit结合, 让TestDriven.NET通过反射找到要测试的内容.

本文所要做的事, 就是尝试做一个类似 TestDriven.NET之于.NET的C++的单元测试驱动工具. 眼下对VS的插件不熟, 所以初步目标只求达到简单好用, 给C++程序员最大程度的减负.

原则上, 一个测试驱动工具, 可以配合不同的测试框架, C++的单元测试框架中, 我选择了 UnitTest++, 这个工具就是驱动 UnitTest++写成的单元测试, 自动为C++项目运行单元测试, 将测试结果显示在 Output工具窗口中.

缺点: 不能自动build, 这意味着如果修改了原代码需要手工build 项目之后, 再运行指定的单元测试.

对程序员的要求:
1. 下载 UnitTest++, 把它编译为 .lib文件, 具体说, 你要编译出分别给 /MT, /MTd, /MD, MDd 4 种不同情况下的.lib.

这一步如果想省事最好别做. 下文我会把编译好的东西贴上来, 给VC2005下使用, 手头有其它版本的编译器时我可以再放出给VS2003, VS2008的文件.

整个测试驱动工具由以下几部分组成:

(A) 驱动. 一个console模式的普通exe文件TestDrivenCpp.exe, 通过参数/file= /line= /target= /debug /tests=  来解析一个C++源文件中给定行上要执行的测试的名字.

(B) 库. 名为UnitTestLib.lib. 有别于UnitTest++本身的库, 这个库提供了一个导出函数:
extern "C" __declspec(dllexport) int __cdecl CPPUnitTest(const char *);

如果你的C++项目输出是DLL, 则由TestDrivenCpp.exe 加载这个Library, 通过GetProcAddress得到其地址, 执行一个普通的导出函数调用.

如果项目输出是EXE, 则不能通过上述方法, 虽然微软允许在EXE中导出函数, 但不能调用! 所以这种情况下就要用到这个库提供的另一个导出函数(模块的导出函数, 并非DLL的展出函数):

extern "C" void __cdecl RunCPPUnitTest(void);
同时需要把下面一行调用放到你的 main/WinMain下的第一行(MFC? 我有日子没用了, 这种程序里看到不main函数, 不知道这句话放在哪里合适)

如下:
int _tmain(int argc, _TCHAR* argv[])
{
    RunCPPUnitTest();  // HERE
    puts("Hello");
    return 0;
}

设置这样一个无参无返回值的函数目的是尽最大可能减少使用者的学习成本. 这个函数的实现在 UnitTestLib.lib库中, 它必需做到绿色环保无毒副作用. 如果以特殊的命令行来执行你的EXE, 则运行单元测试后退出, 否则什么都不能干, 不能干涉程序的正常执行. 整套方案应该允许这个框架被集成在最终产品里也无妨.

关键是用来执行单元测试的命令行参数必需极为特殊, 以至于几乎不可能有任何一个程序员写的程序会接受这样的参数. 拜COM所赐, 我用uuidgen 生成了一个UUID, 只有该程序的第一个参数是这个UUID时, 才会去执行测试的功能.

理论上, 这个方法不严谨, 实用中却不会有问题.

(C) 头文件, TestDrivenCpp.h  用于让你得以调用 (B)中提到的RunCPPUnitTest()函数. 如果程序输出是DLL, 则这个头文件对程序员没有实际需要使用的东西.

(D) VS2005 设置:
(D.1)

(D.2)
添加(A)中的驱动EXE文件作为外部工具



红色框里的内容都很关键, 其中 Arguments那一行太行, 贴在下面:
/line=$(CurLine) /file=$(ItemPath) /target=$(TargetPath)
如果在最后加上 /debug, 可以在实际运行单元测试之前显示一个 Modal对话框, 让你有机会把一个调试器attach到这个进程上.

(D.3) 添加到工具栏


注意这里用的是鼠标拖动, 需要左键按住"External Command 8", 拖到上面的工具栏中合适的位置之后, 松手, 这个命令就跟工具栏上一个按钮关联起来了. 图上已经标明, 为什么是命令8, 一个一个数出来的, 这方面VS做的还不到位. 另外, 拖动时, 所致的命令名字是 External Command 8, 而不是自己指定的名字
TestDriver.C++,  但完成这个添加到工具栏的任务之后, 正常使用时, 显示的还是 TestDriver.C++(应该是TestDriven.C++)

至此, 这个测试框架已经整好了. 与你有没有安装 UnitTest++是独立的两件事, 安装时没有先后顺序的要求.

2. 写单元测试(UnitTest++)

2.1 修改项目属性, 将 UnitTest++.h 所在的目录加到 INCLUDE目录中去. 将我的测试驱动工具的头文件所在目录也添加进去

添加UnitTest++和 本文的测试驱动的lib文件所在的目录到linker的搜索路径上去:


注意这里只需要添加路径, 不需要指定两个需要链接的lib 文件的具体名字, 这是通过在头文件中放置
#pragma comment(lib, lib_file_name.lib)
来实现的, 具体做起来没那么简单, 需要用宏来检测 /MT, /MTd, /MD, /MDd的不同情况链接不同的库文件名, UnitTest++本身并没有这么做, 所以我修改了一点它的头文件, 让它支持这种方式指定库文件.

目的还是一个: 最大限度地减少使用者需要做的工作.

2.2  #include "UnitTest++.h"
     #include "TestDrivenCpp.h"
这两个头文件没有先后次序的要求.

2.3 using namespace UnitTest;

2.4 写单元测试, 在文件范围内, 如下:


TEST(MY_CPP_Unit_Test_Driver)
{

    puts("1");
}

TEST(MY_CPP_Unit_Test_Driver2)
{

    puts(__TIME__);
}
TEST 是宏, 展开后是 UnitTest++的精巧实现. 作为使用者不必关心, 所要做的是保证:
*  起一个唯一的测试名字
*  验证代码是否如期工作(UnitTest++为此提供了很多宏)

2.5 将光标置于TEST 及下面的{}范围中任一处, 在VS2005 的工具栏上点击一个设置好的Button(或通过快捷键)

当前光标下的这个测试将会被执行, 送至标准输出的内容将出现在 Output窗口中, 如下:


TODO:

* 项目输出为 Library的情况没有考虑
* 不能自动build项目
* 做成跟 TestDriven.NET那样的真正的插件
* 支持更多的C++测试框架, 如google test, CppTest

UnitTest++的预编译库(带PDB文件, 以便需要调试), 其中 UnitTest++.h 头文件被我修改了.

文件:UnitTest++Libs_vs2005.zip
大小:596KB
下载:下载

文件:TestDrivenCpp_vs2005.zip
大小:241KB
下载:下载

切记, 这两个文件都是7z格式的, 下载后请更名(cublog不允许上传7z格式的文件)后解压.
阅读(1735) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~