这几天开始学《测试驱动的嵌入式C语言开发(Test Driven Development for Embedded C)》里头用到了Unity和CppUTest测试框架,而因为CppUTest是作者写的,所以书上的代码大部分都是基于CppUTest的。
顺便吐槽下,CppUTest虽然讲是也可以用于C程序,但是还是需要编译器有最简单的C++支持,但我在编嵌入式程序时并不想开CodeWarrior编译器的C++支持。。。所以,就为了学习TDD配置一下吧,后面看看情况,可能实际使用中还是用Unity好了。
为了能配置好运行环境,着实费了我一段功夫。现记录下来以备需。
下载文件
首先,CppUTest原本是用于Linux上g++/gcc编译器的,但我暂时没时间去折腾Linux,所以就想办法直接用vs来学习吧。
从官网或在csdn上找到最新版的代码下载,下载下载的cpputest压缩文件基本长这样:
其有自带的对VS2010的支持的,在docs/里可以看到官方示例的步骤。但我用的是VS2012,基本也是按文档里的步骤来搞,但是遇到了一点坑,可能是项目升级导致的吧,总之没试过2010下是不是直接按说明就能用。
升级项目
如果你装的也是2012或更高版本的visual studio,先要对里头的项目进行升级。
把文件解压后放在任意一个地方,比如我这里放在了E盘。双击其中的.sln文件
然后就会跳出一系列升级指示
单向升级点确定。
然后两个是否覆盖文件就选不覆盖。
然后点击更新编译器
这样我们的项目就更新好了,保存退出。
添加环境变量
然后为了方便使用,我们需要添加一个系统环境变量,我用的是WIN7。
控制面板->高级系统设置->环境变量->新建
变量名:CPPUTEST_HOME
变量值则根据你自己放的位置填,我是放在了E盘所以填的是:E:\cpputest-3.8
然后各种确定就配置好了环境变量。
建立一个简单的TDD工程
工程结构
一个TDD的VS解决方案基本结构包含三个子工程:
- CppUTest工程
直接导入cpputest中准备好的工程,即CPPUTEST_HOME\CppUTest.vcxproj,这样就可以简化整个依赖性配置的过程。 - 单元测试工程
里头放所有单元测试的代码,这样易于维护,而且不会和生产代码混在一起。 - 你的工程
包含项目源码文件,如产品代码。
为了方便起见,下面就不包含产品代码的那个工程了,直接建单元测试工程
建立空解决方案
文件->新建->项目
选择C++项中的空项目,输入项目名,确定。
默认会给你建好一个解决方案加其中的一个工程,这个工程就用来存放你的产品代码;当然也可以直接是测试工程,就像我这里做的。
添加CppUTest工程
在解决方案上右键->添加->现有项目
找到之前那个文件夹中的CppUTest.vcxproj文件并打开。
这样,解决方案中就包含了两个工程,一个是单元测试工程,一个是CppUTest工程。
然后再在解决方案上右键->属性->通用属性->项目依赖项,勾上所有的,确定。
配置测试工程属性
现在还没法使用CppUTest测试框架,主要是还要配置很多东西。
可能最简单的方法就是直接使用CppUTest准备好的项目属性表文件了。
先调出属性管理器,视图->其他窗口->属性管理器。
在属性管理器中的单元测试工程上右键,选择添加现有属性表。
在scripts\VS2010Templates中找到CppUTest_VS2010.props,这样就一下修改了好多项目属性。
然后再在属性管理器中双击刚刚加入的CppUTest_VS2010.props,这样就打开了CppUTest_VS2010.props的属性页,打开用户宏,双击CPPUTEST_FORCED_INCLUDE这一项,让我们把"$(CPPUTEST_HOME)\include\Platforms\VisualCpp\Platform.h;"这一句删掉,当然后面的要保留。
如果不删的话,后面在生成的时候你就会看到这个报错。
然后还要干一件事情。
由于在Debug模式和Release模式下,CppUTest工程的输出库文件的名字稍微有点差,而项目属性文件是按Release模式下配置的,为了在在两种模式下都能很愉快的编译链接,我们在Debug模式(默认)下打开CppUTest工程的属性。
在配置属性->常规->目标文件名中,把最后那个d删掉。这样两个模式下的库文件名就统一了。
简单的测试例程
然后,在UnitTest工程中添加如下两个文件:
AllTests.cpp
#include "CppUTest/CommandLineTestRunner.h"
int main(int ac, char** av)
{
return CommandLineTestRunner::RunAllTests(ac, av);
}
MyFirstTest.cpp
#include "CppUTest/TestHarness.h"
TEST_GROUP(FirstTestGroup)
{
};
TEST(FirstTestGroup, FirstTest)
{
char str[] = "expected";
// 字符串值测试,期望是"expected",实际值在变量str中
STRCMP_EQUAL("expected",str);
// 整型值测试,期望是40,实际值给出32,故意失败
LONGS_EQUAL(40,32);
}
然后在解决方案上右键重新生成(后面只用单元测试工程上重新生成就行)。
你会发现报错了。
如果是长上图这样的报错,莫慌,就是这样的,并不是编译失败了,而是其中有测试未通过,因为我们上面代码故意让LONGS_EQUAL(40,32);这个测试不通过。
为了看到测试结果,我们打开输出窗口,如果找不到的话,点 视图->输出 就会出现。
然后重新生成。
括号里的就是测试结果了,可以看到其提示TEST(FirstTestGroup, FirstTest)这个函数出错,期望是40,给出的是32。然后总结起来,出错一个测试,总共有两项检查,……
解释下为什么生成完他就自动运行了,这是因为在项目属性里头的后期生成事件里设置了自动运行生成的可执行文件,所以可以直接看到结果。
附:为产品代码添加内存泄露检测
CppUTest提供了对内存泄露检测的支持。作完上述步骤后,在XXXXTest文件中如有内存泄露,已经可以检测出来了,但是如果你新开了一个工程放产品代码,那在产品代码中的内存泄露是无法检测出来的。
为了对产品代码工程中的内存泄露进行检测。需要让所有产品文件include以下文件:
$(CPPUTEST_HOME)/include/CppUTest/MemoryLeakDetectorMallocMacros.h
在vs中的做法是,打开产品代码工程的属性页。
配置属性->C/C+±>高级->强制包含文件 中填入这个文件。
然后在测试中就可以检测出测试中没有主动释放的产品代码中内存了。
总结
所以总结起来,这样弄好以后,要在其他项目中添加CppUTest测试框架就只用在现有解决方案中添加现有的工程文件,然后建立一个单元测试工程并添加改好的项目属性表就行了。