﻿<?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>IT博客-www.testage.net---软件测试---RedHat Linux测试环境搭建---MYLIFE-随笔分类-软件测试基础</title><link>http://www.cnitblog.com/jakiegu/category/6704.html</link><description>测试时代的gufeng</description><language>zh-cn</language><lastBuildDate>Fri, 30 Sep 2011 21:31:54 GMT</lastBuildDate><pubDate>Fri, 30 Sep 2011 21:31:54 GMT</pubDate><ttl>60</ttl><item><title> Git版本管理工具中文教程连载4</title><link>http://www.cnitblog.com/jakiegu/archive/2008/09/22/49377.html</link><dc:creator>Test8848-谷峰</dc:creator><author>Test8848-谷峰</author><pubDate>Mon, 22 Sep 2008 11:41:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/09/22/49377.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/49377.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/09/22/49377.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/49377.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/49377.html</trackback:ping><description><![CDATA[&nbsp;<strong><span>提取版本库中的数据</span></strong><strong></strong>
<p align=left><span>这是个很有用的小技巧，如果你对你现在的工作目录下的东西已经不耐烦了，随时可以取出你提交过的东西覆盖掉当前的文件，譬如：</span><span> </span></p>
<p align=left><span>$ git-checkout -f foo.c</span></p>
<div>
<p align=left><strong><span>标定版本</span></strong><strong></strong></p>
</div>
<p align=left><span>在</span><span> git </span><span>中，有两种类型的标签，</span><span>&#8220;</span><span>轻标签</span><span>&#8221;</span><span>和</span><span>&#8220;</span><span>署名标签</span><span>&#8221;</span><span>。</span><span> </span></p>
<p align=left><span>技术上说，一个</span><span>&#8220;</span><span>轻标签</span><span>&#8221;</span><span>和一个分支没有任何区别，只不过我们将它放在了</span><span> </span><span>.git/refs/tags/</span><span> </span><span>目录，而不是</span><span> </span><span>heads</span><span> </span><span>目录。因此，打一个</span><span>&#8220;</span><span>轻标签</span><span>&#8221;</span><span>再简单不过了。</span><span> </span></p>
<p align=left><span>$ git-tag my-first-tag</span></p>
<p align=left><span>如果你打算针对某个</span><span>commit ID</span><span>来打标签，虽然该命令可以通过</span><span>gitk</span><span>里的右键菜单来实现，但是该命令对实际应用是很有帮助的。</span><span> </span></p>
<p align=left><span>$ git-tag mytag f0af<st1:chmetcnv UnitName="F" SourceValue="6283824688" HasSpace="False" Negative="False" NumberType="1" TCSC="0" w:st="on">6283824688f</st1:chmetcnv>9d23426031734657661b54388</span></p>
<p align=left><span>&#8220;</span><span>署名标签</span><span>&#8221;</span><span>是一个真正的</span><span> git </span><span>对象，它不但包含指向你想标记的状态的指针，还有一个标记名和信息，可选的</span><span> PGP </span><span>签名。你可以通过</span><span> </span><span>-a</span><span> </span><span>或者是</span><span> </span><span>-s</span><span> </span><span>选项来创建</span><span>&#8220;</span><span>署名标签</span><span>&#8221;</span><span>。</span><span> </span></p>
<p align=left><span>$ git-tag -s &lt;tag-name&gt;</span></p>
<div>
<p align=left><strong><span>合并外部工作</span></strong><strong></strong></p>
</div>
<p align=left><span>通常的情况下，合并其他的人的工作的情况会比合并自己的分支的情况要多，这在</span><span> git </span><span>中是非常容易的事情，和你运行</span><span> </span><span>git-merge</span><span> </span><span>命令没有什么区别。事实上，远程合并的无非就是</span><span>&#8220;</span><span>抓取（</span><span>fetch</span><span>）一个远程的版本库中的工作到一个临时的标签中</span><span>&#8221;</span><span>，然后再使用</span><span> </span><span>git-merge</span><span> </span><span>命令。</span><span> </span></p>
<p align=left><span>可以通过下面的命令来抓取远程版本库</span><span>: </span></p>
<p align=left><span>$ git-fetch &lt;remote-repository&gt;</span></p>
<p align=left><span>根据不同的远程版本库所使用的通讯协议的路径来替代上面的</span><span> </span><span>remoted-repository</span><span> </span><span>就可以了。</span><span> </span></p>
<p align=left><em><span>Rsync </span></em></p>
<p align=left><span>rsync://remote.machine/patch/to/repo.git/</span><span> </span></p>
<p align=left><em><span>SSH </span></em></p>
<p align=left><span>remote.machine:/path/to/repo.git</span><span><br>or<br></span><span>ssh://remote.machine/patch/to/repo.git/</span><span> </span></p>
<p align=left><span>这是可以上传和下载的双向传输协议，当然，你要有通过</span><span> </span><span>ssh</span><span> </span><span>协议登录远程机器的权限。它可以找出两端的机器提交过的对象集之中相互缺少了那些对象，从而得到需要传输的最小对象集。这是最高效地交换两个版本库之间的对象的方式（在</span><span> git </span><span>兼容的所有传输协议当中）。</span><span> </span></p>
<p align=left><span>下面是个取得</span><span> SSH </span><span>远程版本库的命令例子：</span><span> </span></p>
<p align=left><span>$ git-fetch robin@192.168.1.168:/path/to/gittutorcn.git&nbsp;</span><strong><span>(1)</span></strong></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>(1)</span></strong><span> </span><span>这里<span> robin </span>是登录的用户名，<span>192.168.1.168 </span>是保存着主版本库的机器的<span> IP </span>地址。</span></p>
<p align=left><em><span>Local directory </span></em></p>
<p align=left><span>/path/to/repo.git/</span><span> </span></p>
<p align=left><span>本地目录的情况和</span><span> SSH </span><span>情况是一样的。</span><span> </span></p>
<p align=left><em><span>git Native </span></em></p>
<p align=left><span>git://remote.machine/path/to/repo.git/</span><span> </span></p>
<p align=left><span>git </span><span>自然协议是设计来用于匿名下载的，它的工作方式类似于</span><span> SSH </span><span>协议的交换方式。</span><span> </span></p>
<p align=left><em><span>HTTP(S) </span></em></p>
<p align=left><span>http://remote.machine/path/to/repo.git/</span></p>
<p align=left><span>到这里可能有些朋友已经想到，实际上，我们可以通过</span><span> Rsync, SSH </span><span>之类的双向传输方式来建立类似</span><span> CVS</span><span>，</span><span>SVN </span><span>这样的中心版本库模式的开发组织形式。</span><span> </span></p>
<div>
<p align=left><strong><span>通过电子邮件交换工作</span></strong><strong></strong></p>
</div>
<p align=left><span>读过上一节之后，有的朋友可能要问，如果版本库是通过单向的下载协议发布的，如</span><span> HTTP</span><span>，我们就无法将工作上传到公共的版本库中。别人也不能访问我的机器来抓取我的工作，那怎么办呢？</span><span> </span></p>
<p align=left><span>不必担心，我们还有</span><span> email </span><span>！别忘了</span><span> git </span><span>本来就是为了管理</span><span> Linux </span><span>的内核开发而设计的。所以，它非常适合像</span><span> Linux Kernel </span><span>这样的开发组织形式高度分散，严重依赖</span><span> email </span><span>来进行交流的项目。</span><span> </span></p>
<p align=left><span>下面模拟你参加到《</span><span>Git </span><span>中文教程》的编写工作中来，看看我们可以怎么通过</span><span> email </span><span>进行工作交流。你可以通过下面的命令下载这个项目的版本库。</span><span> </span></p>
<p align=left><span>$ git-clone http://www.bitsun.com/git/gittutorcn.git</span></p>
<p align=left><span>之后，你会在当前目录下得到一个叫</span><span> </span><span>gittutorcn</span><span> </span><span>的目录，这就是你的项目的工作目录了。默认地，它会有两个分支：</span><span> </span><span>master</span><span> </span><span>和</span><span> </span><span>origin</span><span>，你可以直接在</span><span> </span><span>master</span><span> </span><span>下展开工作，也可以创建你自己的工作分支，但是千万不要修改</span><span> </span><span>origin</span><span> </span><span>分支，切记！因为它是公共版本库的镜像，如果你修改了它，那么就不能生成正确的对公共版本库的</span><span> </span><span>patch</span><span> </span><span>文件了。</span><span> </span></p>
<table cellPadding=0 border=0>
    <tbody>
        <tr>
            <td vAlign=top>
            <p align=left><strong><u><span>Note</span></u></strong></p>
            </td>
            <td>
            <p align=left><span>如果你的确修改过 <span>origin</span><span> </span>分支的内容，那么在生成 <span>patch</span><span> </span>文件之前，请用 <span>git-reset --hard</span><span> </span>命令将它逆转到最原始的，没经过任何修改的状态。<span> </span></span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left><span>你可以直接在</span><span> </span><span>master</span><span> </span><span>下开展工作，也可以创建你自己的工作分支。当你对项目做了一定的工作，并提交到库中。我们用</span><span> </span><span>git-show-branch</span><span> </span><span>命令先看下库的状态。</span><span> </span></p>
<p align=left><span>* [master] your buddy's contribution</span></p>
<p align=left><span>&nbsp;! [origin] degining of git-format-patch example</span></p>
<p align=left><span>--</span></p>
<p align=left><span>*&nbsp;[master] your buddy's contribution</span></p>
<p align=left><span>*+ [origin] degining of git-format-patch example</span></p>
<p align=left><span>上面就假设你已经提交了一个叫</span><span> "your buddy's contribution" </span><span>的工作。现在我们来看看怎么通过</span><span> email </span><span>来交流工作了。</span><span> </span></p>
<p align=left><span>$ git-fetch origin<span>&nbsp;&nbsp;&nbsp; </span><strong>(1)</strong></span></p>
<p align=left><span>$ git-rebase origin<span>&nbsp;&nbsp;&nbsp; </span><strong>(2)</strong></span></p>
<p align=left><span>$ git-format-patch origin<span>&nbsp;&nbsp;&nbsp;&nbsp; </span><strong>(3)</strong></span></p>
<p align=left>&nbsp;</p>
<p align=left><strong><span>(1)</span></strong><span>更新<span> origin </span>分支，防止<span> origin </span>分支不是最新的公共版本，产生错误的补丁文件；</span></p>
<p align=left><strong><span>(2)</span></strong><span>将你在<span> master </span>上提交的工作迁移到新的源版本库的状态的基础上；</span></p>
<p align=left><strong><span>(3)</span></strong><span>生成补丁文件；</span></p>
<p align=left><span>上面的几个命令，会在当前目录下生成一个大概名为</span><span> </span><span>0001-your-buddy-s-contribution.txt</span><span> </span><span>补丁文件</span><span>, </span><span>建议你用文本工具查看一下这个文件的具体形式，然后将这个文件以附件的形式发送到项目维护者的邮箱：</span><span> <span><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#118;&#111;&#114;&#116;&#117;&#110;&#101;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;">vortune@gmail.com</a> </span></span></p>
<p align=left><span>当项目的维护者收到你的邮件后，只需要用</span><span> </span><span>git-am</span><span> </span><span>命令，就可以将你的工作合并到项目中来。</span><span> </span></p>
<p align=left><span>$ git-checkout -b buddy-incomming</span></p>
<p align=left><span>$ git-am /path/to/0001-your-buddy-s-contribution.txt</span></p>
<div>
<p align=left><strong><span>用</span></strong><strong><span> Git </span></strong><strong><span>协同工作</span></strong><strong></strong></p>
</div>
<p align=left><span>假设</span><span> Alice </span><span>在一部机器上自己的个人目录中创建了一个项目</span><span> /home/alice/project, Bob </span><span>想在同一部机器自己的个人目录中为这个项目做点什么。</span><span> </span></p>
<p align=left><span>Bob </span><span>首先这样开始：</span></p>
<p align=left><span>$ git-clone /home/alice/project myrepo</span></p>
<p align=left><span>这样就创建了一个保存着</span><span> Alice </span><span>的版本库的镜像的新目录</span><span> "myrepo"</span><span>。这个镜像保存着原始项目的起点和它的发展历程。</span><span> </span></p>
<p align=left><span>接着</span><span> Bob </span><span>对项目做了些更改并提交了这些更改：</span></p>
<p align=left><span>(</span><span>编辑一些文件<span>)</span></span></p>
<p align=left>&nbsp;</p>
<p align=left><span>$ git-commit -a </span></p>
<p align=left>&nbsp;</p>
<p align=left><span>(</span><span>如果需要的话再重复这个步骤<span>)</span></span></p>
<p align=left><span>当他搞定之后，他告诉</span><span> Alice </span><span>将他的东西从</span><span> /home/bob/myrepo </span><span>中引入，她只需要这样：</span><span> </span></p>
<p align=left><span>$ cd /home/alice/project</span></p>
<p align=left><span>$ git pull /home/bob/myrepo</span></p>
<p align=left><span>这样就将</span><span> Bob </span><span>的版本库中的</span><span> "master" </span><span>分支的变化引入了。</span><span> Alice </span><span>也可以通过在</span><span> pull </span><span>命令的后面加入参数的方式来引入其他的分支。</span><span> </span></p>
<p align=left><span>在导入了</span><span> Bob </span><span>的工作之后，用</span><span> "git-whatchanged" </span><span>命令可以查看有什么信的提交对象。如果这段时间里以来，</span><span>Alice </span><span>也对项目做过自己的修改，当</span><span> Bob </span><span>的修改被合并进来的时候，那么她需要手动修复所有的合并冲突。</span><span> </span></p>
<p align=left><span>谨慎的</span><span> Alice </span><span>在导入</span><span> Bob </span><span>的工作之前，希望先检查一下。那么她可以先将</span><span> Bob </span><span>的工作导入到一个新创建的临时分支中，以方便研究</span><span> Bob </span><span>的工作：</span><span> </span></p>
<p align=left><span>$ git fetch /home/bob/myrepo master:bob-incoming</span></p>
<p align=left><span>这个命令将</span><span> Bob </span><span>的</span><span> master </span><span>分支的导入到名为</span><span> bob-incoming </span><span>的分支中（不同于</span><span> git-pull </span><span>命令，</span><span>git-fetch </span><span>命令只是取得</span><span> Bob </span><span>的开发工作的拷贝，而不是合并经来）。接着：</span><span> </span></p>
<p align=left><span>$ git whatchanged -p master..bob-incoming</span></p>
<p align=left><span>这会列出</span><span> Bob </span><span>自取得</span><span> Alice </span><span>的</span><span> master </span><span>分支之后开始工作的所有变化。检查过这些工作，并做过必须的调整之后，</span><span> Alice </span><span>就可以将变化导入到她的</span><span> master </span><span>分支中：</span><span> </span></p>
<p align=left><span>$ git-checkout master</span></p>
<p align=left><span>$git-pull . bob-incoming</span></p>
<p align=left><span>最后的命令就是将</span><span> "bob-incoming" </span><span>分支的东西导入到</span><span> Alice </span><span>自己的版本库中的，稍后，</span><span>Bob </span><span>就可以通过下面的命令同步</span><span> Alice </span><span>的最新变化。</span><span> </span></p>
<p align=left><span>$ git-pull</span></p>
<p align=left><span>注意不需为这个命令加入</span><span> Alice </span><span>的版本库的路径，因为当</span><span> Bob </span><span>克隆</span><span> Alice </span><span>的版本库的时候，</span><span> git </span><span>已经将这个路径保存到</span><span> .git/remote/origin </span><span>文件中，它将会是所以的导入操作的默认路径。</span><span> </span></p>
<p align=left><span>Bob </span><span>可能已经注意到他并没有在他的版本库中创建过分支（但是分支已经存在了）：</span><span> </span></p>
<p align=left><span>$ git branch</span></p>
<p align=left><span>* master</span></p>
<p align=left><span>&nbsp;origin</span></p>
<p align=left><span>"origin" </span><span>分支，它是运行</span><span> "git-clone" </span><span>的时候自动创建的，他是</span><span> Alice </span><span>的</span><span> master </span><span>分支的原始镜像，</span><span> Bob </span><span>应该永远不要向这个分支提交任何东西。</span><span> </span></p>
<p align=left><span>如果</span><span> Bob </span><span>以后决定在另外一部主机上开展工作，那么他仍然需要通过</span><span> SSH </span><span>协议从新克隆和导入（</span><span> Alice </span><span>的版本库）：</span><span> </span></p>
<p align=left><span>$ git-clone alice.org:/home/alice/project/ myrepo</span></p>
<p align=left><span>我们可以使用</span><span> git </span><span>自然协议，或者是</span><span> rsync, http </span><span>等协议的任何一种，详情请参考</span><span> <span><a href="http://www.kernel.org/pub/software/scm/git/docs/git-pull.html" target=_blank>git-pull</a></span></span><span>。</span><span> </span></p>
<span>Git </span><span>同样可以建立类似</span><span> CVS </span><span>那样的开发模式，也就是所有开发者都向中心版本库提交工作的方式，详情参考</span><span> <span><a href="http://www.kernel.org/pub/software/scm/git/docs/git-push.html" target=_blank>git_push</a> </span></span><span>和</span><span> <span><a href="http://www.kernel.org/pub/software/scm/git/docs/cvs-migration.html" target=_blank>git for CVS users</a> </span></span><span>。</span>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/49377.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">Test8848-谷峰</a> 2008-09-22 19:41 <a href="http://www.cnitblog.com/jakiegu/archive/2008/09/22/49377.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用测试优先方法开发用户界面</title><link>http://www.cnitblog.com/jakiegu/archive/2008/01/28/39401.html</link><dc:creator>Test8848-谷峰</dc:creator><author>Test8848-谷峰</author><pubDate>Mon, 28 Jan 2008 09:21:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/01/28/39401.html</guid><description><![CDATA[<h2><span>使用测试优先方法开发用户界面</span></h2>
<span style="FONT-SIZE: 10.5pt"><font size=+0><br><br>
<p style="FONT-SIZE: 9pt">
<p><strong>1、概述</strong><br><br>　　测试优先是<a href="http://www.testage.net/">测试驱动开发</a>(Test-Driven Development, TDD)的核心思想，它要求在编写产品代码前先编写基于产品代码的测试代码。在测试驱动开发的单元测试中，对GUI应用实施自动测试应该是测试驱动开发的软肋之一。由于界面的操作是有由人来完成的，所以要想在GUI中完成单元自动测试是有一定难度的。Kent Beck在它的《测试驱动开发》中就曾提到过这个问题。<br>　　本文将通过一个例子来讲解在测试驱动开发中如何针对GUI进行单元测试。这个例子是 David Astels著的《测试驱动开发实用指南(影印版)》中一个关于影片列表管理的例子。该书中文版即将在国内出版。书中讨论并介绍了开发这个例子的多种方法。笔者将介绍其中的一种，并且为了方便使用C&nbsp;&nbsp;的朋友的学习，书中的代码我用C&nbsp;&nbsp;写了一遍，类名和变量名尽量和原书保持一致，以方便阅读该书的C&nbsp;&nbsp; 读者。在此也要感谢David Astels给我们带来如此精彩的一本书。<br>　　本文叙述背景为：CppUnit1.9.0, Visual C&nbsp;&nbsp; 6.0, Windows2000 pro。文中叙述有误之处，敬请批评指正。如果读者对CppUnit还没有一定的了解，可以先参考笔者的另一篇文章《CppUnit测试框架入门》。<br><br><strong>2、需求分析</strong><br><br>　　对于这个影片管理的应用，我们主要实现增加、删除和显示影片列表的功能。基于这些需求，我们可以画一张GUI草图。<br><br>　　界面的控件主要有：一个显示所有影片的列表listbox控件，一个填写新的影片名的edit控件,一个增加button控件，一个删除button控件。由此，我们的开发目标就十分的明确了。<br><br><strong>3、编写UI测试代码</strong><br><br>　　这部分的UI测试代码主要是测试各个控件是否正确生成并且是可见的，以及测试一些控件的label文字是否正确。<br>　　我们从TestCase继承一个类TestWidgets用于测试窗口，并添加四个测试，分别测试listbox、edit、add button、delete button。</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>class TestWidgets : public CppUnit::TestCase<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST_SUITE(TestWidgets);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testList);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testField);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testAddButton);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testDeleteButton);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST_SUITE_END();<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; TestWidgets();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual ~TestWidgets();<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void setUp();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void tearDown(); <br>&nbsp;&nbsp;&nbsp;&nbsp; void testList();<br>&nbsp;&nbsp;&nbsp;&nbsp; void testField();<br>&nbsp;&nbsp;&nbsp;&nbsp; void testAddButton();<br>&nbsp;&nbsp;&nbsp;&nbsp; void testDeleteButton();<br>private:<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListWindow* m_pWindow;<br>};</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>其中,MovieListWindow是一个窗口类。我们来看看其中的一个测试，请看代码中的注释。</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>void TestWidgets::testAddButton()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //得到btn指针<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CButton* pAddButton = m_pWindow-&gt;GetAddButton();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检查是否生成btn<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT(pAddButton-&gt;m_hWnd);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检查btn是否可见<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(TRUE, ::IsWindowVisible(pAddButton-&gt;m_hWnd));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CString strText;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pAddButton-&gt;GetWindowText(strText);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CString strExpect = "Add";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检查btn的Label文字是否正确<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(strExpect, strText);<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　编译测试代码，编译器会给我们一些出错信息。这要求我们必须马上编写产品代码以让编译通过。首先第一个要实现的产品代码就是MovieListWindow窗口类。 </p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>class AFX_EXT_CLASS MovieListWindow : public CDialog<br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListWindow(CWnd* pParent = NULL); // standard constructor<br>&nbsp;&nbsp;&nbsp;&nbsp; CListBox* GetMovieListBox(){return &amp;m_MovieListBox;};<br>&nbsp;&nbsp;&nbsp;&nbsp; CEdit* GetMovieField(){return &amp;m_MovieField;};<br>&nbsp;&nbsp;&nbsp;&nbsp; CButton* GetAddButton(){return &amp;m_AddBtn;};<br>&nbsp;&nbsp;&nbsp;&nbsp; CButton* GetDeleteButton(){return &amp;m_DeleteBtn;};<br>&nbsp;&nbsp;&nbsp;&nbsp; void Init();<br>&nbsp;&nbsp;&nbsp;&nbsp; // Dialog Data<br>&nbsp;&nbsp;&nbsp;&nbsp; //{{AFX_DATA(MovieListWindow)<br>&nbsp;&nbsp;&nbsp;&nbsp; enum { IDD = IDD_MOVIELISTDLG };<br>&nbsp;&nbsp;&nbsp;&nbsp; CButton m_AddBtn;<br>&nbsp;&nbsp;&nbsp;&nbsp; CButton m_DeleteBtn;<br>&nbsp;&nbsp;&nbsp;&nbsp; CEdit m_MovieField;<br>&nbsp;&nbsp;&nbsp;&nbsp; CListBox m_MovieListBox;<br>&nbsp;&nbsp;&nbsp;&nbsp; //}}AFX_DATA<br>&nbsp;&nbsp;&nbsp;&nbsp; // Overrides<br>&nbsp;&nbsp;&nbsp;&nbsp; // ClassWizard generated virtual function overrides<br>&nbsp;&nbsp;&nbsp;&nbsp; //{{AFX_VIRTUAL(MovieListWindow)<br>&nbsp;&nbsp;protected:<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support<br>&nbsp;&nbsp;&nbsp;&nbsp; //}}AFX_VIRTUAL<br>&nbsp;&nbsp;&nbsp;&nbsp; // Implementation<br>&nbsp;&nbsp;protected:<br>&nbsp;&nbsp;&nbsp;&nbsp;// Generated message map functions<br>&nbsp;&nbsp;&nbsp;&nbsp;//{{AFX_MSG(MovieListWindow)<br>&nbsp;&nbsp;&nbsp;&nbsp;//}}AFX_MSG<br>&nbsp;&nbsp;DECLARE_MESSAGE_MAP()<br>};</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　在MovieListWindow窗口类中我们实现了需要的控件以及针对这些控件的一些方法，如GetMovieListBox()等，本文在此不做详述。编译测试代码和产品代码，检查是否通过。如未通过则继续检查产品代码以使编译和测试通过。 <br><br><strong>4、编写控件行为测试代码</strong> <br><br>　　接下来应该是编写点击add button和delete button的测试代码了。同样,我们从TestCase继承出TestOperation：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>class TestOperation : public CppUnit::TestCase<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST_SUITE(TestOperation);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testMovieList);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testAdd);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST(testDelete);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_TEST_SUITE_END();<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; void testMovieList();<br>&nbsp;&nbsp;&nbsp;&nbsp; void testAdd();<br>&nbsp;&nbsp;&nbsp;&nbsp; void testDelete();<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; void setUp();<br>&nbsp;&nbsp;&nbsp;&nbsp; void tearDown();<br>&nbsp;&nbsp;&nbsp;&nbsp; TestOperation();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual ~TestOperation();<br>private:<br>&nbsp;&nbsp;&nbsp;&nbsp; static CString LOST_IN_SPACE;<br>&nbsp;&nbsp;&nbsp;&nbsp; CStringArray m_MovieNames;<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListWindow* m_pWindow;<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListEditor* m_pEditor;<br>};</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　你会发现，在TestOperation类中出现了一个成员变量MovieListEditor* m_pEditor。类MovieListEditor是一个用来保存影片数据以及对影片数据进行增加，删除操作的管理类。后面我们会给出它的实现。看看setUp()做了什么：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>void TestOperation::setUp()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; //创建一个MovieListEditor实例<br>&nbsp;&nbsp;&nbsp;&nbsp; m_pEditor = new MovieListEditor();<br>&nbsp;&nbsp;&nbsp;&nbsp; m_MovieNames.RemoveAll();<br>&nbsp;&nbsp;&nbsp;&nbsp; //将MovieListEditor中的影片列表拷贝到m_MovieNames,为后面测试作准备<br>&nbsp;&nbsp;&nbsp;&nbsp; for(int n=0; n&lt;m_pEditor-&gt;GetMovies()-&gt;GetSize(); n&nbsp;&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_MovieNames.Add(m_pEditor-&gt;GetMovies()-&gt;GetAt(n));<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>我们来看看添加影片的测试，请看代码注释：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>void TestOperation::testAdd()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; //拷贝一份movie list<br>&nbsp;&nbsp;&nbsp;&nbsp; CStringArray MovieNamesWithAddition;<br>&nbsp;&nbsp;&nbsp;&nbsp; for(int n=0; n&lt;m_MovieNames.GetSize(); n&nbsp;&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MovieNamesWithAddition.Add(m_MovieNames.GetAt(n));<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieNamesWithAddition.Add(LOST_IN_SPACE);<br>&nbsp;&nbsp;&nbsp;&nbsp; //生成窗口<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListWindow *pWindow = new MovieListWindow(m_pEditor);<br>&nbsp;&nbsp;&nbsp;&nbsp; pWindow-&gt;Init();<br>&nbsp;&nbsp;&nbsp;&nbsp; //填写新的影片的名称<br>&nbsp;&nbsp;&nbsp;&nbsp; CEdit* pEdit = pWindow-&gt;GetMovieField();<br>&nbsp;&nbsp;&nbsp;&nbsp; pEdit-&gt;SetWindowText(LOST_IN_SPACE);<br>&nbsp;&nbsp;&nbsp;&nbsp; //点击add btn <br>&nbsp;&nbsp;&nbsp;&nbsp; CButton* pBtn = pWindow-&gt;GetAddButton();<br>&nbsp;&nbsp;&nbsp;&nbsp; ::SendMessage(pBtn-&gt;m_hWnd, BM_CLICK, 0, 0);<br>&nbsp;&nbsp;&nbsp;&nbsp; //检查列表控件中是否已加入新的影片<br>&nbsp;&nbsp;&nbsp;&nbsp; CListBox* pListBox = pWindow-&gt;GetMovieListBox();<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), pListBox-&gt;GetCount());<br>&nbsp;&nbsp;&nbsp;&nbsp; //检查列表控件中影片名是否正确<br>&nbsp;&nbsp;&nbsp;&nbsp; CString strNewMovieName;<br>&nbsp;&nbsp;&nbsp;&nbsp; pListBox-&gt;GetText(pListBox-&gt;GetCount()-1, strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp; //销毁窗口<br>&nbsp;&nbsp;&nbsp;&nbsp; pWindow-&gt;DestroyWindow();<br>&nbsp;&nbsp;&nbsp;&nbsp; delete pWindow;<br>&nbsp;&nbsp;&nbsp;&nbsp; pWindow = NULL;<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>编译后会有出错信息，主要的错误有：<br>a)、我们把m_pEditor保存在MovieListWindow中了，这需要我们修改原来的MovieListWindow的构造函数。 <br>b)、没有MovieListEditor类。<br><br>MovieListEditor的实现如下：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>class AFX_EXT_CLASS MovieListEditor <br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListEditor();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual ~MovieListEditor();<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual CStringArray* GetMovies(){return &amp;m_arMovieList;};<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void Add(CString strMovie){m_arMovieList.Add(strMovie);};<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void Delete(int nIndex){m_arMovieList.RemoveAt(nIndex);};<br>private:<br>&nbsp;&nbsp;&nbsp;&nbsp; CStringArray m_arMovieList;<br>};</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>再次编译,已经通过.运行测试,发现在：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), pListBox-&gt;GetCount());</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>测试通不过。检查后知道原因是，我们在测试代码里：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>::SendMessage(pBtn-&gt;m_hWnd, BM_CLICK, 0, 0);</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>给add button发送了点击按钮的消息，但是在MovieListWindow 窗口中我们没有加入消息的响应函数，因此测试没有通过。赶紧添加消息响应函数。</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>void MovieListWindow::OnClickAddButton() <br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; UpdateData();<br>&nbsp;&nbsp;&nbsp;&nbsp; CString strNewMovieName;<br>&nbsp;&nbsp;&nbsp;&nbsp; m_MovieField.GetWindowText(strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp; if("" != strNewMovieName)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_pEditor-&gt;Add(strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_MovieListBox.AddString(strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>编译、测试、通过。 <br><br><strong>5、Mock Objects</strong> <br><br>　　在删除操作的单元测试中，我们遇到的一个问题是，影片列表的数据应该是保存在一个文本文件或者数据库当中的，如果我们编写的测试依赖于这些实际的文件或数据库，那么我们的测试就会受制于这些外部的资源。一旦文件或者数据库里的数据发生变化，必然会波及到我们的测试代码，从而产生错误的测试信息。前面的MovieListEditor中我们没有加入一些初始化的数据,在测试删除操作时会遇到一些问题 。<br>　　这里，我们引入Mock Objects。Mock Objects用来模拟外部复杂的资源（如数据库，网络连接等），使UI可以测试那些依赖于这些复杂外界资源的模块。例如在测试一个跟数据库有关系的模块时，我们并不一定要建立一个真实的数据库连接，而只需建立一个Mock Objects就可以了。测试所需的数据都存在于这个Mock Objects。可以说，Mock Objects为我们提供了一个轻量级的、可控制的、高效的模型。<br>　　在本例中，影片的增加、删除都会跟文件或数据库操作发生关系。这时我们就可以利用Mock Objects来隔离测试代码与文件或数据库。使用Mock Objects一般有以下几个步骤：<br>a)、定义一个外部资源的接口.(这个接口一般是可以在重构过程中提炼出来的)。<br>b)、定义一个Mock Objects，从外部资源的接口继承下来，实现外部资源的接口。<br>c)、创建一个Mock Objects，并设置它的内部期望值。<br>d)、把创建的这个Mock Objects传递给需要测试的模块进行操作。<br>e)、操作完毕后将Mock Objects内部的状态与期待状态比较。 现在我们就根据这个步骤来实现本例子中的Mock Objects.通过对前面的代码进行重构,我们可以提炼出一个接口MovieListEditor：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>class AFX_EXT_CLASS MovieListEditor <br>{<br>public:<br>　　　　MovieListEditor();<br>　　　　virtual ~MovieListEditor();<br>public:<br>　　　　virtual CStringArray* GetMovies()=0;<br>　　　　virtual void Add(CString strMovie)=0;<br>　　　　virtual void Delete(int nIndex)=0;<br>};</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　请注意它和前面我们定义的MovieListEditor的不同。接下来，我们应该定义一个Mock Objects，当然它是从MovieListEditor继承下来的：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>class mockEditor : public MovieListEditor<br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; mockEditor();<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual ~mockEditor();<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual CStringArray* GetMovies(){return &amp;m_arMovieList;};<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void Add(CString strMovie){m_arMovieList.Add(strMovie);};<br>&nbsp;&nbsp;&nbsp;&nbsp; virtual void Delete(int nIndex){m_arMovieList.RemoveAt(nIndex);};<br>private:<br>&nbsp;&nbsp;&nbsp;&nbsp; CStringArray m_arMovieList;<br>};</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>然后给这个Mock Objects设置初识值，我们选择在它的构造函数里进行。</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>mockEditor::mockEditor()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; m_arMovieList.Add("Star Wars");<br>&nbsp;&nbsp;&nbsp;&nbsp; m_arMovieList.Add("Star Trek");<br>&nbsp;&nbsp;&nbsp;&nbsp; m_arMovieList.Add("Stargate");<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　我们添加了三个影片用于测试。接着，应该把这个MockObjects的一个实例传递给需要测试的模块。这里就是我们要测试的UI(MovieListWindow)。</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>m_pEditor = new mockEditor();<br>&nbsp;&nbsp; MovieListWindow *pWindow = new MovieListWindow(m_pEditor);</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>最后我们来看看经过修改后的新的测试添加影片的方法：</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>void TestOperation::testAdd()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; //拷贝一份movie list<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CStringArray MovieNamesWithAddition;<br>&nbsp;&nbsp;&nbsp;&nbsp; for(int n=0; n&lt;m_MovieNames.GetSize(); n&nbsp;&nbsp;)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MovieNamesWithAddition.Add(m_MovieNames.GetAt(n));<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieNamesWithAddition.Add(LOST_IN_SPACE);<br>&nbsp;&nbsp;&nbsp;&nbsp; //生成窗口<br>&nbsp;&nbsp;&nbsp;&nbsp; MovieListWindow *pWindow = new MovieListWindow(m_pEditor);<br>&nbsp;&nbsp;&nbsp;&nbsp; pWindow-&gt;Init();<br>&nbsp;&nbsp;&nbsp;&nbsp; //填写新的影片的名称<br>&nbsp;&nbsp;&nbsp;&nbsp; CEdit* pEdit = pWindow-&gt;GetMovieField();<br>&nbsp;&nbsp;&nbsp;&nbsp; pEdit-&gt;SetWindowText(LOST_IN_SPACE);<br>&nbsp;&nbsp;&nbsp;&nbsp; //点击add btn <br>&nbsp;&nbsp;&nbsp;&nbsp; CButton* pBtn = pWindow-&gt;GetAddButton();<br>&nbsp;&nbsp;&nbsp;&nbsp; ::SendMessage(pBtn-&gt;m_hWnd, BM_CLICK, 0, 0);<br>&nbsp;&nbsp;&nbsp;&nbsp; //检查列表控件中是否已加入新的影片<br>&nbsp;&nbsp;&nbsp;&nbsp; CListBox* pListBox = pWindow-&gt;GetMovieListBox();<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), pListBox-&gt;GetCount());<br>&nbsp;&nbsp;&nbsp;&nbsp; //将Mock Objects的内部数据和期望值进行比较<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), <br>&nbsp;&nbsp;&nbsp;&nbsp; m_pEditor-&gt;GetMovies()-&gt;GetSize());<br>&nbsp;&nbsp;&nbsp;&nbsp; //检查列表控件中影片名是否正确<br>&nbsp;&nbsp;&nbsp;&nbsp; CString strNewMovieName;<br>&nbsp;&nbsp;&nbsp;&nbsp; pListBox-&gt;GetText(pListBox-&gt;GetCount()-1, strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, strNewMovieName);<br>&nbsp;&nbsp;&nbsp;&nbsp; //将Mock Objects的内部数据和期望值进行比较<br>&nbsp;&nbsp;&nbsp;&nbsp; int nIndex = m_pEditor-&gt;GetMovies()-&gt;GetSize();<br>&nbsp;&nbsp;&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, m_pEditor-&gt;GetMovies()-&gt;GetAt(nIndex-1));<br>&nbsp;&nbsp;&nbsp;&nbsp; //销毁窗口<br>&nbsp;&nbsp;&nbsp;&nbsp; pWindow-&gt;DestroyWindow();<br>&nbsp;&nbsp;&nbsp;&nbsp; delete pWindow;<br>&nbsp;&nbsp;&nbsp;&nbsp; pWindow = NULL;<br>}</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　请注意，这里测试的数据都是mockEditor里的，而且在UI进行添加操作后，还将mockEditor内部的状态与期待状态做了比较。</p>
<br>
<p><br>
<table cellSpacing=1 cellPadding=15 width="95%" align=center bgColor=#333333 border=0>
    <br>
    <tbody>
        <br>
        <tr bgColor=#ffffff>
            <br>
            <td align=left bgColor=#e6e6e6>CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), m_pEditor-&gt;GetMovies()-&gt;GetSize());<br>&nbsp;&nbsp; CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, m_pEditor-&gt;GetMovies()-&gt;GetAt(nIndex-1));</td>
        </tr>
    </tbody>
</table>
</p>
<br>
<p>　　其他删除操作的测试跟添加类似，在此不做详述。至此，我们就完成了这个GUI应用程序的开发。</p>
<br>
<p><br><strong>6、源码说明</strong> <br><br>　　本文附带的代码包括三个Project，分别是Movie、 GuiTestFirst、AppMovieList.Movie是产品代码.GuiTestFirst是测试代码。AppMovieList是使用Movie输出的产品代码而写的应用程序，它从MovieListEditor继承出一个新的影片管理类 MyEditor。它主要是演示如何使用我们提炼出来的MovieListEditor接口。例如你可以实现CXmlMovieListEditor，CAccessMovieListEditor等等。进入GuiTestFirst打开所有这些工程。</p>
<br>
<p><br><strong>7、总结</strong> <br><br>a)、对GUI应用实施测试优先开发方法，这在测试驱动开发中并不是必须的，可根据开发的实际情况来选择。 <br>b)、我们通过引入Mock Objects，我们使测试代码和外部复杂的资源隔离开来，同时也使我们能够从中既有代码中提炼出清晰的接口，使代码整洁可用。 <br><br><strong>8、参考资料</strong> </p>
<br>
<ul><br>
    <li>《测试驱动开发实用指南(影印版)》David Astels <br>
    <li>《测试驱动开发(中文版)》Kent Beck <br>
    <li>《Endo-Testing: Unit Testing with Mock Objects》Tim Mackinnon, Steve Freeman, Philip Craig </li>
</ul>
</font></span>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/39401.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">Test8848-谷峰</a> 2008-01-28 17:21 <a href="http://www.cnitblog.com/jakiegu/archive/2008/01/28/39401.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SmartHttpd java代码修改带来的http_server性能优化</title><link>http://www.cnitblog.com/jakiegu/archive/2008/01/14/38841.html</link><dc:creator>Test8848-谷峰</dc:creator><author>Test8848-谷峰</author><pubDate>Mon, 14 Jan 2008 08:07:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/01/14/38841.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/38841.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/01/14/38841.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/38841.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/38841.html</trackback:ping><description><![CDATA[<p>SmartHttpd 1.0.0.2<br><br>1.基于完整端口的静态Web服务器的服务器套件，可支持各种<a href="http://www.testage.net/"><a href="http://www.testage.net/">测试</a>工具</a>.<br>2.适合用户高并发高负荷的场合的静态页面场合特别适用于图片服务器.<br>3.为了满足高<span class=highlight><strong><font color=#ff0000>性能</font></strong></span>的要求以及保持灵活性,动态内容采用预留接口实现.<br>4.配置说明<br>1)配置文件为当前目录下的config.ini<br>2)ListenPort:指定绑定端口<br>3)WWWRoot:静态页面内容所在目录<br>4)AcceptExNum:可承受的突然并发连接(建议采用默认值)<br>5)MaxWorkThread:工作线程数目(0代表根据CPU数目指定最佳值)<br>6)ServiceName:安装后服务的名称<br><br>较1.0.0.1有多处改进,在17000个并发持续连接的情况下仍有良好的<span class=highlight><strong><font color=#ff0000>性能</font></strong></span>,改天放Linux下基于epoll模型的<br>——————————————————————————————————————————————————<br></p>
<p>//完成端口的部分代码<br><br>//主线程<br><br>//创建原始完成端口句柄<br>hIOCP=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);<br>if(NULL==hIOCP)<br>{<br>//错误处理<br>}<br><br>//创建监听套节字 ListenPort()封装了bind listen等函数<br>SocketListen=ListenPort();<br><br>//绑定原始完成端口句柄到监听套节字SocketListen<br>if(NULL==CreateIoCompletionPort((HANDLE)SocketListen,hIOCP,0,0))<br>{<br>//错误处理<br>}<br><br>//投递AcceptEx<br>{<br>unsigned long Num;<br>unsigned long AcceptExNum;<br><br>//GetConfigInt封装了读取配置文件<br>AcceptExNum=GetConfigInt("Httpd","AcceptExNum",2000);<br><br>//投递多个AccpetEx,PostAccpetEx封装了AccpetEx<br>for(Num=0;Num&lt;AcceptExNum;Num++)<br>{<br>PostAccpetEx(NULL);<br>}<br>}<br><br>{ //启动工作线程<br><br>unsigned long MaxWorkThread;<br>unsigned long threadcount;<br>DWORD dwThreadId;<br>HANDLE* hThread;<br><br>//读取配置文件中开启线程的数目<br>MaxWorkThread=GetConfigInt("Httpd","MaxWorkThread",2);<br><br>//0代表根据CPU数目设置线程数目 CPU数目*2+2<br>if(MaxWorkThread&lt;1)<br>{<br>SYSTEM_INFO systemInfo;<br>GetSystemInfo(&amp;systemInfo);<br>MaxWorkThread=systemInfo.dwNumberOfProcessors*2+2;<br>}<br><br>hThread=HeapAlloc(GetProcessHeap(),0,sizeof(HANDLE)*MaxWorkThread);<br><br>for(threadcount=0;threadcount&lt;MaxWorkThread;threadcount++)<br>{<br>hThread[threadcount]=CreateThread(NULL,0,WorkerThread,hIOCP,0,&amp;dwThreadId);<br>}<br><br>//等待工作线程退出,主线程进入阻塞状态<br>WaitForMultipleObjects(MaxWorkThread,hThread,TRUE,INFINITE);<br><br>for(threadcount=0;threadcount&lt;MaxWorkThread;threadcount++)<br>{<br>CloseHandle(hThread);<br>}<br><br>HeapFree(GetProcessHeap(),0,hThread);<br><br>}<br><br><br><br>//工作线程<br><br>//LOOP循环条件,根据情况设定相应的条件<br>while(LOOP)<br>{<br>BOOL Succes;<br>P_IOCPDATA IOCPData;//自己定义的数据结构<br>unsigned long TransByte;<br>LPWSAOVERLAPPED lpWsaOverlapped;<br><br>//获取完成通知<br>//完成端口的精华就在这里<br>//通过调用GetQueuedCompletionStatus,线程可以获得原子性的时间段,避免了线程切换的开销<br>Succes=<br>GetQueuedCompletionStatus(<br>hIOCP,<br>&amp;TransByte,<br>(unsigned long*)&amp;IOCPData,<br>&amp;lpWsaOverlapped,<br>INFINITE);<br><br>if(Succes&amp;&amp;NULL!=lpWsaOverlapped&amp;&amp;TransByte)//完全正常<br>{<br>IOCPData=(P_IOCPDATA)lpWsaOverlapped;<br><br>//根据不同的I/O类型进行不同的处理<br>switch(IOCPData-&gt;IOType)<br>{<br>//AcceptEx具有连接和接受的功能<br>case AcptDone:<br>{<br>//有一个AccpetEx完成,再投递一个AccpetEx<br>PostAccpetEx(NULL);<br>{<br>//此段代码可以不调用,未发生错误,不知道为什么,<br>//如果不调用的话,句柄不会很多,不会超出限制<br>/*<br>//更行AccpetEx返回的套节字状态<br>int ErrorCode;<br>ErrorCode=<br>setsockopt(<br>IOCPData-&gt;Socket, <br>SOL_SOCKET,<br>SO_UPDATE_ACCEPT_CONTEXT,<br>(char*)&amp;SocketListen,<br>sizeof(SocketListen)<br>);<br>if(SOCKET_ERROR==ErrorCode)<br>{<br>Test();<br>FreeIOCPData(IOCPData);<br>continue;<br>}<br>*/<br>}<br>//绑定AccpetEx返回的套节字到hIOCP<br>if(hIOCP!=CreateIoCompletionPort((HANDLE)IOCPData-&gt;Socket,hIOCP,(unsigned long)IOCPData,0))<br>{<br>Test();<br>FreeIOCPData(IOCPData);<br>continue;<br>}<br>//我写的是一个Web服务器,下面的是Http消息头的分析处理<br>if(HttpParseHead(IOCPData))<br>{<br>Respons(IOCPData);<br>}<br>break;<br><br>}<br>//...... 其他处理情况<br>}<br>}<br>//.... 出现错误情况下的处理<br>}<br></p>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/38841.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">Test8848-谷峰</a> 2008-01-14 16:07 <a href="http://www.cnitblog.com/jakiegu/archive/2008/01/14/38841.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>