﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-只有有耐心圆满完成简单工作的人，才能够轻而易举地完成困难的事。-随笔分类-CPPUnit专栏</title><link>http://www.cppblog.com/leetaolion/category/4356.html</link><description>Only those who have the patience to do simple things perfectly ever acquire the skill to do difficult things easily. </description><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 00:53:00 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 00:53:00 GMT</pubDate><ttl>60</ttl><item><title>温度计的寓言</title><link>http://www.cppblog.com/leetaolion/archive/2008/03/14/44525.html</link><dc:creator>创建更好的解决方案</dc:creator><author>创建更好的解决方案</author><pubDate>Fri, 14 Mar 2008 12:49:00 GMT</pubDate><guid>http://www.cppblog.com/leetaolion/archive/2008/03/14/44525.html</guid><wfw:comment>http://www.cppblog.com/leetaolion/comments/44525.html</wfw:comment><comments>http://www.cppblog.com/leetaolion/archive/2008/03/14/44525.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/leetaolion/comments/commentRss/44525.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/leetaolion/services/trackbacks/44525.html</trackback:ping><description><![CDATA[故事是这样的：有一个由5个年轻人合伙建立的小软件公司YoungSoft co. ltd，成立不久，业务不多，刚刚实现收支平衡。<br>一场突发的流行性感冒，使得市面上体温计奇缺，价格也是水涨船高。一个农民企业家投资50w创建的温度计公司Good Metor，一直惨淡经营，这么好的机会，自然不会放过。市场上价格最高的是一种能记录一段时间内人体温度变化曲线，并判断使用者是否患上这种可怕的流感的智能体温计Smart Thermometer。其实现很简单，无非是在传统的体温计上加一块单片机，体温计和单片机满大街都是，而单片机内程序的掌握在AnyMetor几家大型温度计厂商手中，从不外泄。于是Good Metor决定赌一把，找人开发这种软件，希望借此咸鱼翻身。<br>一个偶然的机会，GoodMetor来到了YoungSoft，双方一拍即合。由YongSoft承担Smart Thermometer软件的开发工作，价钱谈定100kRMB，定金30k，双方约定3个月之内交货，按期交货另有奖金10kRMB。<br>这种工作对YongSoft简直是手到擒来，哥5个齐上阵，不肖两个月的时间，GoodSmartMetro v1.0版本就Release了。剩下一个月的时间，5位年轻人有足够的时间喝喝茶，聊聊天，做些零碎的活，等着GoodMetor来验收。<br>在两个半月的时候，GoodMetor就坐不住了，匆匆忙忙来到YoungSoft，当得知软件已经正式发布的时候，自然喜不自禁，当日即付清货款和奖金，回厂批量生产。<br>可怕的流感又持续了两个月，终于的到了有效的控制。GoodMetor公司的超低价只能体温计在这次抗击流感中在为国家和社会作出卓越贡献的同时，更为公司赚了个盆满钵满，500w的纯利润啊，GoodMetor的老大做梦都没想到过。<br>CPI高企，钱存银行不明智，GoodMetor老大决定：扩大生产。生产啥呢？现在人都讲究健康饮食，饮用水上更是严格把关，科学研究表明，烧开水时如果水温上升曲线和推荐曲线吻合，人喝了之后会更健康。于是一种监测水温控制火候的新型灶具HealthOven应运而生。老大拍板，就搞它了。<br>于是又找到了YoungSoft。<br>半年过去了，YoungSoft的几个年轻人做了几个小项目，人员结构也发生了改变，大家有了更明确的分工。一个联系业务，分析需求，少量编码的HuManager，三个编码的WenProgramers，一个负责测试，少量编码的WuTester。<br>HuManager分析认为，HealthOven和SmartThermometer之间，除测量范围从人的体温上升到水的沸点之外，温度计中的液体也从水银换成了酒精。GoodSmartMetro v1.0中采用了一个免费的液体体积温差换算库MercuryLib，所以做的时候省去了大把的工作。酒精和水银的物理属性相差太大，这次换算的功能怕是要自己开发了，暂定名为LiquidLib，支持水银和酒精的体积温差换算。但是以前直接调用MercuryLib的模块必须进行解耦和测试，多数不能要了。<br>最终，双发约定5个月内软件交付使用，总价50wRMB，定金20wRMB，及时交付奖金5wRMB。<br>在GoodSmartMetro v2.0开发进行到第3个月的时候，WenProgramer了解到，产业升级过程中，温控炉GreenIron炼钢能大幅降低能耗，并在一次项目组CCB会议上提了出来，大家一致认为YoungSoft要加速发展，必须着眼未来。GoodSmartMetro v2.0版本可以为将来打开GreenIron市场早做准备。不能再吃MercuryLib那样的亏了。LiquidLib模块需要良好的通用性，将来可以作为平台的基础模块，支持各种不同液体做成温度计的温控软件开发。<br>GreenIron设备商采用不同的液体做成的温度计，在这个问题上，业界还没有统一的标准。<br>（未完待续...）<br>
<img src ="http://www.cppblog.com/leetaolion/aggbug/44525.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/leetaolion/" target="_blank">创建更好的解决方案</a> 2008-03-14 20:49 <a href="http://www.cppblog.com/leetaolion/archive/2008/03/14/44525.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用CPPUnit做单元测试（原文在E文全翻中）</title><link>http://www.cppblog.com/leetaolion/archive/2007/05/26/24882.html</link><dc:creator>创建更好的解决方案</dc:creator><author>创建更好的解决方案</author><pubDate>Sat, 26 May 2007 02:53:00 GMT</pubDate><guid>http://www.cppblog.com/leetaolion/archive/2007/05/26/24882.html</guid><wfw:comment>http://www.cppblog.com/leetaolion/comments/24882.html</wfw:comment><comments>http://www.cppblog.com/leetaolion/archive/2007/05/26/24882.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/leetaolion/comments/commentRss/24882.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/leetaolion/services/trackbacks/24882.html</trackback:ping><description><![CDATA[<p>用CPPUnit做单元测试<br>例子程序下载：<a href="http://www.codeproject.com/library/Using_CPPUnit/my_tests.zip">http://www.codeproject.com/library/Using_CPPUnit/my_tests.zip</a><br>CPPUnit最新版本免费下载：<br><a href="http://cppunit.sourceforge.net/">http://cppunit.sourceforge.net/</a><br>CPPUnit是基于C++的单元测试框架，可以有效提高开发的系统质量。<br>引言：<br>QA过程常采用两种测试方法：<br>1、单元测试（acceptance测试）：为软件系统中的每一个逻辑单元制定的一系列验证方法。仅测试单元的功能，而不考虑各个单元之间的协作关系。<br>2、系统测试（集成测试）：测试系统的功能，尤其是各单元模块之间的协作关系。<br>下面要讲的是如何采用CPPUnit对C/C++工程进行单元测试。<br>文章假设读者熟悉单元测试的概念及其重要性。<br>单元测试设计：<br>想一下开发团队中常常出现的一种场景：程序员正在使用Debugger工具测试代码。采用Debugger工具可以可以随时随地检查每个变量。步步跟踪，检查变量的值是否异常。Debugger是一种强有力的调试工具，但是调试速度相当慢，并且包含不少错误。在这种情况下调试是让人崩溃的。这些复杂有大量重复的验证方法是可以通过自动化的手段完成的，需要做的是选择合适的工具并编写少量代码。<br>下面要介绍的工具叫做&#8220;单元测试框架&#8221;，借助这种工具，可以通过编写一些小的模块来完成模块（可以是类、函数和库）的单元测试。<br>下面来看一个例子：编写一个小的模块，主要功能是求两数之和。其C语言代码如下：<br>BOOL addition(int a, int b)<br>{<br>&nbsp;&nbsp;&nbsp; return (a + b);<br>}<br>测试单元编写成另外一个模块（C函数）。该模块测试所有可能的求两数之和的组合，通过返回True或False来判断被测模块是否通过了测试。代码如下：<br>BOOL additionTest()<br>{<br>&nbsp;&nbsp;&nbsp; if ( addition(1, 2) != 3)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if ( addition(0, 0) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if ( addition(10, 0) != 10)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if ( addition(-8, 0) != -8)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if ( addition(5, -5) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if ( addition(-5, 2) != -3)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if ( addition(-4, -1) != -5)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; return (TRUE);<br>}<br>测试的情况包括：<br>正数+正数<br>0+0<br>正数+0<br>负数+0<br>正数+负数<br>负数+正数<br>负数+负数<br>每一次测试都是通过对比被测模块的返回值和期望值，如果二者不同，返回FALSE。如果最终返回TRUE，说明模块通过了所有的测试。<br>这个用以测试其他模块的小模块（函数）被称为Test Case, 其中包含了程序员需要对被测单元的一系列检查。每一个确认（对被测单元的一次调用）都必须和被测单元相对应。在这个例子中，检查了&#8220;求和操作&#8221;在操作数符号不同的情况下的运行情况。当然了，还需要另外写一些Test Case来验证其他情况下的运行情况。比如其他一些常见的加法组合。例子如下：<br>int additionPropertiesTest()<br>{<br>&nbsp;&nbsp;&nbsp; //conmutative: a + b = b + a<br>&nbsp;&nbsp;&nbsp; if ( addition(1, 2) != addition(2, 1) )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; //asociative: a + (b + c) = (a + b) + c<br>&nbsp;&nbsp;&nbsp; if ( addition(1, addition(2, 3)) != addition(addition(2, 1), 3 ) )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; //neutral element: a + NEUTRAL = a<br>&nbsp;&nbsp;&nbsp; if ( addition(10, 0) != 10 )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; //inverse element: a + INVERSE = NEUTRAL<br>&nbsp;&nbsp;&nbsp; if ( addition(10, -10) != 0 )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;return (FALSE);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; return (TRUE);<br>}<br>上面的例子测试了多个数据相加顺序不同的情况。<br>上述的两个Test Case组成了一个Test Suite，Test Suite是指用来测试同一被测单元的一组Test Case。<br>在开发被测模块时必须同时编写这些Test Case和Test Suite的代码，被测模块变更时，要同时变更（有时需要增加）相应的Test Case和Test Suite。<br>举例来说，当求和模块升级为可以对小数求和的模块，就必须变更Test Case和Test Suite，加入诸如addDecimalNumbersTest之类的Test Case。<br>极限编程建议程序员在编写目标模块之前就开发出所有单元测试中要用到的Test Case。其主要理由是：一旦程序员处于开发过程之中，那么他就进入了一个持续改进的阶段，必须同时考虑单元模块功能、需要公布的接口、需要给方法传递的参数、外部访问、内部行为等等。在编写目标单元之前通过开发Test Case，可以对需要考虑的这些因素有更好的了解，这样编写目标模块与其他方法相比速度会更快，代码的质量也会更好。<br>每当开发团队需要发布新版本的时候，都要进行彻底的单元测试。所有的单元必须通过单元测试，这样就可以发布成功的版本。如果有1个或以上的单元没有通过所有的测试，Bug就出现了。遇到这种情况就需要在进行测试，如果需要的话还需要增加新的Test Case，检查可以使Bug再现的所有情况。如果新的Test Case可以使Bug重现，就可以修正这个Bug，然后再进行测试，如果模块通过了测试，就可以认为Bug已经修正，可以发布新的无Bug版本了。<br>为每一个发现的Bug添加新的Test Case是很有必要的，因为Bug会反复出现，当其重复出现时需要有效的测试来检测Bug。这样的话，Test Bettery会逐渐膨胀直至覆盖所有的历史Bug和潜在的错误。<br>测试工具：<br>有两个小伙子，一个叫Kent Beck，另一个叫Eric Gamma，他们写了一系列的Java类，希望可以把测试做的尽可能自动化，并称之为JUnit，JUnit使整个单元测试界产生的很大的震动。其他的开发者们把JUnit的代码移植到其他语言上，构建了一大系列称为xUnit框架的产品。其总包括C/C++的CUnit和CPPUnit，Delphi的DUnit，Visual Basic的VBUnit，.NET平台上的NUnit，等等。<br>所有这些框架都采用同样的规则，对语言的依赖性很小，熟悉其中一个框架就能够熟练应用其他框架。<br>下面要讲的是如何通过使用CPPUnit来编写测试代码并提高单元的质量。<br>CPPUnit采用面向对象的编程方法，中间会遇到诸如封装、继承、多态这些概念。另外，CPPUnit采用C++ SEH（Structured Exception Handling），所以还会遇到异常的概念，以及throw, try, finally, catch这些指令。<br>CPPUnit<br>每一个Test Case都需要在TestCase类的派生类中定义。TestCase类中包含了许多基本的功能，比如运行测试、在Test Suite中注册Test Case等。<br>比如在需要写一个在磁盘上存储数据的小模块的时候，模块（定义为DiskData类）主要实现两个功能：读取数据和装载数据。例程如下：<br>typedef struct _DATA<br>{<br>&nbsp;&nbsp;&nbsp; int number;<br>&nbsp;&nbsp;&nbsp; char string[256];<br>}DATA, *LPDATA;</p>
<p>class DiskData<br>{<br>public:<br>&nbsp;&nbsp;&nbsp; DiskData();<br>&nbsp;&nbsp;&nbsp; ~DiskData();</p>
<p>&nbsp;&nbsp;&nbsp; LPDATA getData();<br>&nbsp;&nbsp;&nbsp; void setData(LPDATA value);</p>
<p>&nbsp;&nbsp;&nbsp; bool load(char *filename);<br>&nbsp;&nbsp;&nbsp; bool store(char *filename);</p>
<p>private:<br>&nbsp;&nbsp;&nbsp; DATA m_data;<br>};</p>
<p>此时，首先要做的事情不是弄明白上面的代码是如何变出来的，而是要确定上面所定义的类是否完成了设计的全部功能——正确地读取和存储数据。</p>
<p>为此，需要设计一个新的Test Suite，其中包含两个Test Case：一个读取数据、一个存储数据。</p>
<p>使用CPPUnit</p>
<p>最新版本的CPPUnit可以在<a href="http://cppunit.sourceforge.net/">http://cppunit.sourceforge.net/</a>上免费下载到，其中包含所有的库文件、文档、例子程序和其他有趣的素材。</p>
<p>在Win32环境下，可以在VC++（6.0或更新版本）中使用CPPUnit，由于CPPUnit采用的是ANSI C++，所以可应用于C++ Builder等开发环境中的版本较少。<br>构建库文件的步骤可以在CPPUnit发布版本的INSTALL-WIN32.txt文件中找到。构<br>建好库文件之后就可以着手编写Test Suite了。</p>
<p>在VC++下编写单元测试程序的步骤如下：<br>&nbsp;创建一个基于MFC的对话框应用程序（或者文档应用程序）<br>&nbsp;开启RTTI:Project Settings -&gt; C++ -&gt; C++ Language<br>&nbsp;在include目录中加入CPPUnit\include:Tools -&gt; Options -&gt; Directories -&gt; Include<br>&nbsp;连接cppunitd.lib(静态连接)或者cppunitd_dll.lib(动态连接)，testrunnerd.lib。如果是在&#8220;Release&#8221;配置下编译，同样需要连接这些库文件，只是需要把名称中的&#8220;d&#8221;字母去掉。<br>&nbsp;拷贝testrunnerd.dll文件到可执行文件夹的下面（或者路径下的其他文件夹中），如果是动态连接的话，还需要拷贝cppunitd_dll.dll（&#8220;Release&#8221;配置下需要拷贝testrunner.dll和cppunit_dll.dll）。</p>
<p>配置好之后即可以着手进行单元测试类编码了。</p>
<p>待测试的DiskData类，主要实现两个功能：读取和存储磁盘上的数据。要测试这两个功能，需要两个Test Case：一个负责读取数据、一个负责存储数据。<br>下面是单元测试类的定义：<br>#if !defined(DISKDATA_TESTCASE_H_INCLUDED)<br>#define DISKDATA_TESTCASE_H_INCLUDED</p>
<p>#if _MSC_VER &gt; 1000<br>#pragma once<br>#endif // _MSC_VER &gt; 1000</p>
<p>#include &lt;cppunit/TestCase.h&gt;//为了从基类TestCase派生新的测试类<br>#include &lt;cppunit/extensions/HelperMacros.h&gt;//方便快速定义测试类的宏</p>
<p>#include "DiskData.h"</p>
<p>class DiskDataTestCase : public CppUnit::TestCase<br>{<br>&nbsp;&nbsp;&nbsp; CPPUNIT_TEST_SUITE(DiskDataTestCase);//定义Test Suite的起点<br>&nbsp;CPPUNIT_TEST(loadTest);//定义Test Case<br>&nbsp;CPPUNIT_TEST(storeTest);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_TEST_SUITE_END();//定义Test Suite的终点</p>
<p>public:<br>&nbsp;&nbsp;&nbsp; void setUp();<br>&nbsp;&nbsp;&nbsp; void tearDown();</p>
<p>protected:<br>&nbsp;&nbsp;&nbsp; void loadTest();<br>&nbsp;&nbsp;&nbsp; void storeTest();</p>
<p>private:<br>&nbsp;&nbsp;&nbsp; DiskData *fixture;<br>};</p>
<p>#endif</p>
<p>例程中，DiskDataTestCase类重载了两个方法：setUp()和tearDown()。这两个方法在Test Case开始和结束的时候自动运行。</p>
<p>测试逻辑是在两个Protected方法中实现的，稍后要涉及到如何为测试逻辑编码。&nbsp;</p>
<p>例程的最后定义了指向DiskData类型数据的指针fixture，用以保存测试过程中的目标对象。setUp()是初始化函数，在调用每一个Test Case之前调用setUp()，同时负责初始化目标对象。Test Case运行过程中要使用fixture。在每一个Test Case运行结束之后，调用tearDown()销毁fixture。这样，每次运行Test Case时所使用的都是新产生的fixture。</p>
<p>测试步骤如下：<br>&nbsp;开启测试程序<br>&nbsp;点击&#8220;Run&#8221;按键<br>&nbsp;调用setUp()方法：初始化fixture<br>&nbsp;调用第一个Test Case函数<br>&nbsp;调用tearDown()方法：释放fixture<br>&nbsp;调用setUp()方法：初始化fixture<br>&nbsp;调用第二个Test Case函数<br>&nbsp;调用tearDown()方法：释放fixture&nbsp;<br>&nbsp;...</p>
<p>经过编码：<br>#include "DiskDataTestCase.h"</p>
<p>CPPUNIT_TEST_SUITE_REGISTRATION(DiskDataTestCase);</p>
<p><br>void DiskDataTestCase::setUp()<br>{<br>&nbsp;&nbsp;&nbsp; fixture = new DiskData();<br>}</p>
<p>void DiskDataTestCase::tearDown()<br>{<br>&nbsp;&nbsp;&nbsp; delete fixture;<br>&nbsp;&nbsp;&nbsp; fixture = NULL;<br>}</p>
<p><br>void DiskDataTestCase::loadTest()<br>{<br>&nbsp;&nbsp;&nbsp; // our load test logic<br>}</p>
<p><br>void DiskDataTestCase::storeTest()<br>{<br>&nbsp;&nbsp;&nbsp; // our store test logic<br>}</p>
<p>现在，编码已经变得非常简单了：setUp()和tearDown()实现了创建、释放fixture，下面要做的就是为loadTest()、storeTest()编码了。</p>
<p>Test Case编码</p>
<p>搞清楚需要测试那些方面之后的工作是编码实现。可以通过使用库函数、第三方库函数、Win32 API或者C/C++操作符和指令的内部属性。</p>
<p>有时需要辅助的文件或者数据库表来存储正确的数据。在本例中，通过对比内部不数据和外部文件的数据来判断结果是否正确。</p>
<p>当出现错误时（比如内部数据和外部数据不同），需要抛出异常。可以通过CPPUNIT_FAIL(message)宏实现，也可以通过assertions宏实现。<br>以下是一些常用的assertion宏:<br>&nbsp;CPPUNIT_ASSERT(condition): 检查condition，如为false，抛出异常<br>&nbsp;CPPUNIT_ASSERT_MESSAGE(message, condition): 检查condition，如为false，抛出异常，并显示预先设定的信息<br>&nbsp;CPPUNIT_ASSERT_EQUAL(expected,current): 检查expected与current的值是否相等，抛出异常，显示expected和current的值<br>&nbsp;CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,current): 检查expected的值与actual的值是否相等，抛出异常，显示expected,current的值，并显示预先设定的信息<br>&nbsp;CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,current,delta): 检查expected, current之差是否小于delta，如果不小于，显示expected和current的值</p>
<p>下面讲一下loadTest编码的编码构想：首先需要一个外部文件，其中存储这一个DATA型数据，文件的创建方式并不重要，关键是要保证里面的数据的正确性。然后，要进行的操作是检查load函数从外部文件中读出的数据和实现存在其中的数据是否一致。代码如下：<br>//<br>// 前提：外部文件中已存储了正确的数据。<br>//<br>#define AUX_FILENAME&nbsp;&nbsp;&nbsp; "ok_data.dat"<br>#define FILE_NUMBER&nbsp;&nbsp;&nbsp; 19<br>#define FILE_STRING&nbsp;&nbsp;&nbsp; "this is correct text stored in auxiliar file"</p>
<p>void DiskDataTestCase::loadTest()<br>{<br>&nbsp;&nbsp;&nbsp; // 相对路径转化为绝对路径<br>&nbsp;&nbsp;&nbsp; TCHAR&nbsp;&nbsp;&nbsp; absoluteFilename[MAX_PATH];<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; size = MAX_PATH;</p>
<p>&nbsp;&nbsp;&nbsp; strcpy(absoluteFilename, AUX_FILENAME);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &amp;size) );</p>
<p>&nbsp;&nbsp;&nbsp; // 执行操作<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT( fixture-&gt;load(absoluteFilename) );</p>
<p>&nbsp;&nbsp;&nbsp; // 通过assertion检查运行结果<br>&nbsp;&nbsp;&nbsp; LPDATA&nbsp;&nbsp;&nbsp; loadedData = fixture-&gt;getData();</p>
<p>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT(loadedData != NULL);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(FILE_NUMBER, loadedData-&gt;number);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT( 0 == strcmp(FILE_STRING, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fixture-&gt;getData()-&gt;string) );<br>}</p>
<p>通过这样一个简单的Test Case测试了4个可能存在的错误：<br>&nbsp;load函数返回值<br>&nbsp;getData函数返回值<br>&nbsp;number结构的成员值<br>&nbsp;string结构的成员值<br>&nbsp;<br>storeTest要复杂一些，因为需要把fixture中的数据存储到临时文件中，之后打开两个文件（新的临时文件和外部文件），读出数据并比照内容。代码如下：</p>
<p>void DiskDataTestCase::storeTest()<br>{<br>&nbsp;&nbsp;&nbsp; DATA&nbsp;&nbsp;&nbsp; d;<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; tmpSize, auxSize;<br>&nbsp;&nbsp;&nbsp; BYTE&nbsp;&nbsp;&nbsp; *tmpBuff, *auxBuff;<br>&nbsp;&nbsp;&nbsp; TCHAR&nbsp;&nbsp; absoluteFilename[MAX_PATH];<br>&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; size = MAX_PATH;</p>
<p>&nbsp;&nbsp;&nbsp; // 填充结构体<br>&nbsp;&nbsp;&nbsp; d.number = FILE_NUMBER;<br>&nbsp;&nbsp;&nbsp; strcpy(d.string, FILE_STRING);</p>
<p>&nbsp;&nbsp;&nbsp; // 相对路径转化为绝对路径</p>
<p>&nbsp;&nbsp;&nbsp; strcpy(absoluteFilename, AUX_FILENAME);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &amp;size) );</p>
<p>&nbsp;&nbsp;&nbsp; // 执行操作<br>&nbsp;&nbsp;&nbsp; fixture-&gt;setData(&amp;d);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT( fixture-&gt;store("data.tmp") );</p>
<p>&nbsp;&nbsp;&nbsp; // 读出两文件的内容并对比<br>&nbsp;&nbsp;&nbsp; // ReadAllFileInMemory 是一个分配缓冲区的外部函数<br>&nbsp;&nbsp;&nbsp; // 把文件内容存入其中. 调用函数负责释放缓冲区.<br>&nbsp;&nbsp;&nbsp; tmpSize = ReadAllFileInMemory("data.tmp", tmpBuff);<br>&nbsp;&nbsp;&nbsp; auxSize = ReadAllFileInMemory(absoluteFilename, auxBuff);</p>
<p>&nbsp;&nbsp;&nbsp; // 文件不存在则抛出异常<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_MESSAGE("New file doesn't exists?", tmpSize &gt; 0);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_MESSAGE("Aux file doesn't exists?", auxSize &gt; 0);</p>
<p>&nbsp;&nbsp;&nbsp; // 文件大小可获得，否则抛出异常<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT(tmpSize != 0xFFFFFFFF);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT(auxSize != 0xFFFFFFFF);</p>
<p>&nbsp;&nbsp;&nbsp; // 缓冲区必须可用，否则抛出异常<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT(tmpBuff != NULL);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT(auxBuff != NULL);</p>
<p>&nbsp;&nbsp;&nbsp; // 两个文件的大小必须和DATA一致<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL((DWORD) sizeof(DATA), tmpSize);<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(auxSize, tmpSize);</p>
<p>&nbsp;&nbsp;&nbsp; // 两文件的内容必须一致<br>&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT( 0 == memcmp(tmpBuff, auxBuff, sizeof(DATA)) );</p>
<p>&nbsp;&nbsp;&nbsp; delete [] tmpBuff;<br>&nbsp;&nbsp;&nbsp; delete [] auxBuff;</p>
<p>&nbsp;&nbsp;&nbsp; ::DeleteFile("data.tmp");<br>}</p>
<p>启动用户界面<br>最后，看看如何显示基于MFC的用户界面对话框（事先在其内部编译了TestRunner.dll）。</p>
<p>打开实现类的文件（ProjectNameApp.cpp），把下列代码复制到InitInstance方法中：<br>#include &lt;cppunit/ui/mfc/TestRunner.h&gt;<br>#include &lt;cppunit/extensions/TestFactoryRegistry.h&gt;</p>
<p>BOOL CMy_TestsApp::InitInstance()<br>{<br>&nbsp;&nbsp;&nbsp; ....</p>
<p>&nbsp;&nbsp;&nbsp; // 声明Test Runner，用以注册的测试填入其中，并运行<br>&nbsp;&nbsp;&nbsp; CppUnit::MfcUi::TestRunner runner;</p>
<p>&nbsp;&nbsp;&nbsp; runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() );</p>
<p>&nbsp;&nbsp;&nbsp; runner.run();&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; return TRUE;<br>}<br>&nbsp;<br>很简单，不是吗？只需要定义一个"runner"实例，添加注册过的test（test是通过CPP文件中的CPPUNIT_TEST_SUITE_REGISTRATION宏注册的），就可以运行run函数了。</p>
<p>编译、运行，开始你的单元测试吧:)</p>
<img src ="http://www.cppblog.com/leetaolion/aggbug/24882.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/leetaolion/" target="_blank">创建更好的解决方案</a> 2007-05-26 10:53 <a href="http://www.cppblog.com/leetaolion/archive/2007/05/26/24882.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>