单元测试实施解惑(一)
在刚过去的一个月中,我完成了一个小软件框架的设计与实现。期间由于并行开发的需要,在没有对代码完成单元测试的情形下我将之check in到了SVN的主干上,随后的心情很是忐忑。因为我知道我一定会犯错(事实也证明在单元测试完成之前就发现了两个缺陷),害怕给他人带来麻烦并影响自己的形象。
另外,由于对刚加入项目的单元测试环境完全不了解,所以在该框架的前期开发工作中我并没有运用单元测试渐进地保证软件质量。其结果可想而知,我花了不少时间去修复编码过程中遗留下来的低级错误。需要指出的是,所在项目是一个大型嵌入式系统,项目编译一次就得20分钟左右,调试效率可以想象。与之不同的是,单元测试可以在Linux/Cygwin这样的环境中完成,加上可以使用gdb进行调试,开发效率提高一个数量级应不是问题。
由于我深刻地体会到了单元测试对工作与生活质量的重要性,所以持“真正高质高效的软件开发工程师,一定是那些深刻理解并切实实施单元测试的人”这一观点。然而,就我过去几年的工作见闻来看,发现身边绝大多数的工程师并没有真正用心去拥抱单元测试。出现这样的状况,我认为存在一定的原由,因此想借本文谈谈一些认识。
有相当一部分工程师是因为并不了解什么是单元测试而没能尝到单元测试的好处。一部分人认为,平时开发工作中的调试其实就是单元测试(参见《明析单元测试》),有的则因为没有花时间学习单元测试而对之不了解。对于这些新手,我并不打算在本文对之“扫盲”,请下载ClearRTOS源码(点击下载)和本文的附件自行学习。 ClearRTOS 是我为《专业嵌入式软件开发》一书所设计的、可在Cygwin和Linux环境中运行的“实时”操作系统,其中涵盖有单元测试方面的内容,更具体的信息见文后。
另外的一部分人尽管实施过单元测试,但却没能从中受益,甚至得出“单元测试无用”的结论。这部分人的困惑是我最想在这里加以指出的。归结起来,我认为实施过程中的“缝太大”是其中一大主因。
不少团队将项目的产品代码与单元测试代码加以区别对待,这是产生“缝”的第一大根源。表现之一是,编译产品代码与编译单元测试代码采用完全不同的编译环境,程序员在日常工作中需要不停地在两个编译环境中进行切换。这种方式很容易让工程师感到麻烦,甚至因此遭到抵制或弃用。好的方式是,将单元测试代码的编译环境与产品代码进行无缝整合。比如,在嵌入式系统项目中做到运行“make release”或“make debug”实现产品代码编译,运行“make unitest”完成单元测试代码编译(ClearRTOS项目就是这么做的)。做到编译环境的无缝整合需要团队中存在精通编译环境构建语言(比如Makefile)的专家。很不幸的是,这方面的专家少得可怜,团队对这方面知识的精进也因为没有意识到其重要性而缺乏动力。
表现之二是,产品代码与单元测试代码区别维护。采用这种方式的团队,很容易在工作中将单元测试摆到更低的位置,开发过程会先以产品代码为主,然后(有时间时)再补上单元测试代码。这种方式很容易降低实施单元测试的效果,且容易因为产品代码与单元测试代码的不同步而带来更大的维护成本。实际上,实施单元测试的一大好处就是在对产品代码进行变更时,通过及时实施单元测试保证软件质量。及时维护单元测试代码从短期和长期都具有很好的经济性,而非象我们想象的那样成本高昂。
产生“缝”的第二大根源,是因为工程师在单元测试过程中不能方便地获得代码覆盖报告。以我的观点,代码覆盖报告应在开发环境中运行象“make creport”这样的命令而轻松获得(ClearRTOS项目同样实现了这一点)。我看到过一些项目,工程师为了获得覆盖报告,需要登录到一个Web服务器上才能查看,而非在工程师的工作机器上随手获取。
产生“缝”的另一大根源与单元测试的“打桩”方法有关。被广为采用的方法是通过使用象Cmockery这样的单元测试框架以打桩的形式,将被测模块独立出来。这种方式尽管被广泛采用,但我觉得所需付出的成本还是很高的。因为“桩的世界”与“产品代码世界”存在很大的“缝隙”,维护期间需要不停地在两个“世界”进行切换,更好的方式是将桩代码融入到产品代码中(细节我想通过另一篇文章给出)。
尽管我认为单元测试是一种有效的质量保障方法,但其有效性在工程界和学术界都存在一定的争议。
关于ClearRTOS
ClearRTOS现在是一个开源项目,读者可以通过SVN获取将来的新版本。为了能在ClearRTOS项目中获得HTML格式的代码覆盖报告,读者需在Linux/Cygwin中安装LCOV(链接)。
在ClearRTOS项目中获取代码覆盖报告的步骤如下:
1)运行“tar xzvf ClearRTOS.tar.gz”解压。
2)运行“cd ClearRTOS/build”进入编译目录。
3)运行“make unitest”编译单元测试程序。
4)运行“make test”执行单元测试程序。
5)运行“make creport”获取单元测试报告。报告可用IE、Chrome等浏览器打开ClearRTOS/build/coverage/index.html文件进行浏览。通过点击网页中的相关链接可以查看到每个源文件的代码覆盖情况。