﻿<?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博客网-testage.net---软件测试---RedHat Linux测试环境搭建---MYLIFE</title><link>http://www.cnitblog.com/jakiegu/</link><description>测试时代的gufeng</description><language>zh-cn</language><lastBuildDate>Mon, 08 Sep 2008 06:08:39 GMT</lastBuildDate><pubDate>Mon, 08 Sep 2008 06:08:39 GMT</pubDate><ttl>60</ttl><item><title>LoadRunner端口映射</title><link>http://www.cnitblog.com/jakiegu/archive/2008/07/15/46746.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Tue, 15 Jul 2008 07:18:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/07/15/46746.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/46746.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/07/15/46746.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/46746.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/46746.html</trackback:ping><description><![CDATA[<p>LR的Port端口映射的视频演示<br></p>
<p><a href="http://www.tudou.com/programs/view/MSDYcTpzrfU/">http://www.tudou.com/programs/view/MSDYcTpzrfU/</a></p>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/46746.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-07-15 15:18 <a href="http://www.cnitblog.com/jakiegu/archive/2008/07/15/46746.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux内核测试三部曲（3）</title><link>http://www.cnitblog.com/jakiegu/archive/2008/06/19/45881.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Thu, 19 Jun 2008 05:23:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/06/19/45881.html</guid><description><![CDATA[阶段三 容错性测试<br><br>你的新内核已经通过了测试的前两个阶段了吗？现在，可以开始实验了。那就是，做一些任何一个正常人在普通工作中都不会做的蠢事，因为没人会知道那些蠢事会导致内核崩溃。那到底需要做些什么事呢？好的，如果存在一个&#8220;标准的&#8221;程序的话，那它当然早就被包含在一些测试套件中了。<br><br>例如，第三阶段可以从拔掉和重插USB设备开始。从理论上讲，重插一个USB设置不应该会产生任何事情，至少从用户的角度看来。如果USB子系统存在bug,连续进行很多次这样的测试就可能会导致内核崩溃(这个测试如果出现问题，说明了之前未曾有人在类似系统配置下尝试这个测试)。<br>接下来，你可以编写一个脚本，连续读取/proc目录中文件的内容，等等。一句话，在这阶段，你应该做些普通用户永远不会做的事(或者极常少做：为什么有人会不断重复地挂载和卸载某个文件系统呢？)
<img src ="http://www.cnitblog.com/jakiegu/aggbug/45881.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-06-19 13:23 <a href="http://www.cnitblog.com/jakiegu/archive/2008/06/19/45881.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux内核测试三部曲（2）</title><link>http://www.cnitblog.com/jakiegu/archive/2008/06/19/45879.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Thu, 19 Jun 2008 05:20:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/06/19/45879.html</guid><description><![CDATA[阶段二（自动测试）<br><br>测试的下一阶段是，使用专门设计的程序来检查特定的内核子系统能否正常工作。我们还执行内核的退化以及性能测试。后者对于内核开发员（还有我们）来说，相当地重要，因为它使得我们能识别出损害性能的改变。比如，如果我们把内核从2.6.x-rc1升级到2.6.x-rc2后，有种文件系统的性能却降低了10%，查找导致这种情况发生的补丁无疑是个好主意。<br><br>关于内核的自动测试，我们推荐你使用AutoTest 套件(<a href="http://test.kernel.org/autotest/">http://test.kernel.org/autotest/</a>)。该套件包含了很多测试应用程序以及统计工具，而且相当简单的用户界面。<br><br>为了安装AutoTest你以root帐户可以进入到/usr/local 目录并运行<br><br># svn checkout svn://test.kernel.org/autotest/trunk autotest<br><br>虽然通常不推荐以root帐户运行类似命令，但这个命令应该是安全的，除非你不信任你的DNS服务器，因为它只是下载一些文件，并保存到/usr/local下。除此之外，你还得以root帐户运行AutoTest，因为它的一些测试要求超级用户权限才能完成。所以，你不应该在生产系统上使用AutoTest:在极端情况下，保存在进行特权级测试的系统中的数据可能会受到损坏甚至毁坏，我们相信你不喜欢你的产品数据会发生这种情况。<br>根据设计，AutoTest是非交互式性的，所以一旦启动，你就不需要再关心它了(当然了，如果有坏事真的发生了，你就要不得不恢复系统了，但这是另外一个话题)。要启动测试，你可以到/usr/local/autotest/client目录下 (我们假设AutoTest被安装到了/usr/local下),然后以root身份执行：<br><br># bin/autotest tests/test_name/control<br><br>这里，test_name是位于目录/usr/local/autotest/client/tests中的目录名称，该目录中含有你想要进行的测试。控制文件tests/test_name/control包含了为AutoTest准备的指令。最简单的情况下，只需要一个这样的指令，也就是：<br><br>job.run_test(&#8217;test_name&#8217;)<br><br>这里，test_name是包含控制文件的目录名称。更加复杂的控制文件的内容可能类似下面：<br><br>job.run_test(&#8217;pktgen&#8217;, &#8217;eth0&#8217;, 50000, 0, tag=&#8217;clone_skb_off&#8217;)<br>job.run_test(&#8217;pktgen&#8217;, &#8217;eth0&#8217;, 50000, 1, tag=&#8217;clone_skb_on&#8217;)<br><br>这里，测试名称后面的字符串表示的是应当要传递到测试程序的参数。你可以修改这些参数，但是首先要阅读测试程序的文档以及脚本tests/test_name/test_name.py(比如tests/pktgen/pktgen.py)。实际上AutoTest就是使用该脚本来启动测试的(可能你已经注意到，<br>AutoTest脚本是用Python语言写的)。脚本sts/test_name/test_name.py的执行结果保存在目录results/default/test_name/中，该目录中的status文件包含着标识测试是否已经成功完成的信息。想取消正在进行的测试，按Ctrl+C即可。<br>如果你想连续运行一串测试，最好的做法是准备单独一个文件，该文件中包含着多个AutoTest的指令。这个文件中的指令和上面提到的控制文件中的指令类似。比如，文件samples/all_tests包含有运行所有可以测试指令，其中前五行如下<br><br>job.run_test(&#8217;aiostress&#8217;)<br>job.run_test(&#8217;bonnie&#8217;)<br>job.run_test(&#8217;dbench&#8217;)<br>job.run_test(&#8217;fio&#8217;)<br>job.run_test(&#8217;fsx&#8217;)<br><br>要运行这个文件中的指令所请求的全部测试，你可以做使用命令bin/autotest samples/all_tests。但是要记得这要花很长的时间才能完成。类似地，要运行定制的一串测试，得把为AutoTest准备的指令们放到一个文件里面，然后给这个文件起个名称，这个名称将作为命令行的一个参数传递给autotest.要并行地运行几个测试，你得准备一个特殊的控制文件，这个控制文件包含的指令如下:<br><br>def kernbench():<br>job.run_test(&#8217;kernbench&#8217;, 2, 5)<br>def dbench():<br>job.run_test(&#8217;dbench&#8217;)<br>job.parallel([kernbench], [dbench])<br><br>在测试执行的过程序中，在任何时间里，你都可以通过按Ctrl+C来停止它们。<br>为了造福那些不喜欢命令行和配置文件的朋友，ATCC (AutoTest Control Center )已经被发明出来了。如果你运行它，比如通过命令ui/menu,将会得到一个用菜单驱动的简单界面，你可以通过界面选择测试和profiling工具，浏览测试结果，还可以通过它来限制范围，配置它们。<br>如果你厌烦了AutoTest套件中包含的一系列工具，可以访问站点<a href="http://ltp.sourceforge.net/tooltable.php">http://ltp.sourceforge.net/tooltable.php</a> 。该站点有可以用于linux内核测试的全面的工具清单。<br><br>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/45879.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-06-19 13:20 <a href="http://www.cnitblog.com/jakiegu/archive/2008/06/19/45879.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux内核测试三部曲（1）</title><link>http://www.cnitblog.com/jakiegu/archive/2008/06/19/45878.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Thu, 19 Jun 2008 05:17:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/06/19/45878.html</guid><description><![CDATA[通常，有多种测试linux内核的方法，但我们只关注下面这4种：<br><br>1. 在平时的工作中使用内核的测试版本.<br><br>2. 在新内核上运行专用的测试套件，比如LTP.<br><br>3. 新内核安装后，做些非常规的事情.<br><br>4. 新内核安装后，测试系统的性能.<br><br>当然，测试过程它们可以混合运用，所以可以认为它们是测试过程的不同阶段。<br><br><br>阶段一:内核的冒烟测试<br><br>内核测试的第一阶段很简单：我们尝试着启动内核，然后把它用在平时的工作中。<br><br>&#8226; 在以全功能配置启动系统之前，推荐先用init=/bin/bash命令行参数来启动内核。它使得内核只启动一个bash进程。这样，你可以检查在最小配置下是否能正常挂载和卸载文件系统，然后，你可以测试一些更加复杂的内核功能，比如挂起到disk或RAM的缓冲。这种情况下，唯一被挂载了的模块是位于initrd映象中的那些模块。提到 了initrd映象。通常，你可以参考你的boot loader文档来获取更多关于手工向内核传递命令行的信息(我们的观点是，如果使用的是GRUB，会更容易操作些。)<br><br>&#8226; 接着，建议以runlevel 2来启动系统(通常，是通过把数字2作为命令行参数传递给内核)，这时，网络服务器和X服务器还没启动(你的系统有可能为这个用途使用的是另一个runlevel，尽管不大可能，所以你应该查看/etc/inittab文件确认一下)。在这个配置下，你可以检查网络接口是否能工作，你还可以尝试手工启动X服务器来确认它不会崩溃掉。<br><br>&#8226; 最后，根据需要，你可以把系统启动到runlevel 5(也就是全功能)或者3（也就是除了X，全功能开启）<br>现在，你已经可以按平时的样子使用系统一段时间了。但是，如果想快点测试内核，你可以执行一些典型的操作，比如下载一些文件，读取邮件，浏览一些站点，抓取一些音轨(我们假设，使用的是合法买来的音频CD)，刻录一张CD或DVD等等操作来连续地检查是否有任何一项会出错。出错的话，就意味着内核有问题存在了
<img src ="http://www.cnitblog.com/jakiegu/aggbug/45878.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-06-19 13:17 <a href="http://www.cnitblog.com/jakiegu/archive/2008/06/19/45878.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LR监视器方面的2个问题和解决方案</title><link>http://www.cnitblog.com/jakiegu/archive/2008/05/15/43738.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Thu, 15 May 2008 04:12:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/05/15/43738.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/43738.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/05/15/43738.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/43738.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/43738.html</trackback:ping><description><![CDATA[&nbsp;
<p><span>1.</span><span>监控</span><span>IIS</span><span>的时候，</span><span>counter</span><span>并没有出现在</span><span>add measurements</span><span>里面</span></p>
<p><img height=446 alt="" src="http://www.cnitblog.com/images/cnitblog_com/jakiegu/1.GIF" width=1272 border=0><br><span></span></p>
<p><span>解决方案：</span></p>
<table cellSpacing=0 cellPadding=0 border=1>
    <tbody>
        <tr>
            <td>
            <p align=left><a name=wp36392></a><span>Some Win 2000 counters cannot be monitored from an NT machine. </span></p>
            </td>
            <td>
            <p align=left><a name=wp36394></a><span>Run the Controller or Tuning Console on a Win 2000 machine.</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p>&nbsp;</p>
<p><span>2. add measurements</span><span>的时候出现了如下画面<br></span></p>
<p><img height=201 src="http://www.cnitblog.com/images/cnitblog_com/jakiegu/2.GIF" width=638 border=0><br></p>
<p><span>解决方案：</span></p>
<p><span>在</span><span>Server</span><span>上面创建一个用户，用户名和密码和监控机上面的要一样</span></p>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/43738.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-05-15 12:12 <a href="http://www.cnitblog.com/jakiegu/archive/2008/05/15/43738.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>出现"/var/lib/mysql/mysql.sock“不存在的解决方法</title><link>http://www.cnitblog.com/jakiegu/archive/2008/03/12/40843.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Wed, 12 Mar 2008 06:44:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/03/12/40843.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/40843.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/03/12/40843.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/40843.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/40843.html</trackback:ping><description><![CDATA[这种情况大多数是因为你的mysql是使用rpm方式安装的，它会自动寻找 /var/lib/mysql/mysql.sock 这个文件，<br>通过unix socket登录mysql。<br>常见解决办法如下：<br>1、<br>创建/修改文件 /etc/my.cnf，至少增加/修改一行<br>[mysql]<br>[client]<br>socket = /tmp/mysql.sock<br>#在这里写上你的mysql.sock的正确位置，通常不是在 /tmp/ 下就是在 /var/lib/mysql/ 下<br><br>2、<br>指定IP地址，使用tcp方式连接mysql，而不使用本地sock方式<br>#mysql -h127.0.0.1 -uuser -ppassword<br><br>3、<br>为 mysql.sock 加个连接，比如说实际的mysql.sock在 /tmp/ 下，则<br>#ln -s /tmp/mysql.sock /var/lib/mysql/mysql.sock即可
<img src ="http://www.cnitblog.com/jakiegu/aggbug/40843.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-03-12 14:44 <a href="http://www.cnitblog.com/jakiegu/archive/2008/03/12/40843.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>测试时代-谷峰</dc:creator><author>测试时代-谷峰</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">测试时代-谷峰</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>apache快速重起的最佳方式</title><link>http://www.cnitblog.com/jakiegu/archive/2008/01/28/39400.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Mon, 28 Jan 2008 09:09:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/01/28/39400.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/39400.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/01/28/39400.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/39400.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/39400.html</trackback:ping><description><![CDATA[<p><br>大多数<a href="http://bbs.testage.net/forum-8-1.html">测试工程师</a>再做<a href="http://www.testage.net/">性能测试</a>的时候，都接触到基于unix平台的Apache服务器，我一般都用Apachect1 restart来启动<br>可是有时候效果并不好！所以这里推荐下列启动方式</p>
<p>1.起动Apache有很多种方法-但实质都是调用httpd--fei一般只用Apachectl好用简单apachectl是控制Apache的脚本-start,stop,restart就不用说了<br></p>
<p>2.参数---configtest是看配置文件的语法有没有错误-有就会指出哪行-什么错--主要是调试用.status是如果你加载了mod_status模块后调用lynx显示apache的工作状态.<br></p>
<p>3.主要是graceful---完美的重起--我推荐每次重起都用它不用restart--那么它与restart的不同---可以写一篇文章啦--概括的说restart向apache主进程发送一个SIGNUP信号--而graceful发的是SIGUSER１信号－－发送HUP信号给主进程将导致结束它的子进程, 就想TERM（stop)信号一样,但是主进程将不会终止. 它将重新读取配置文件, 并且重新打开所有的日志文件. 然后重新产生所有子进程并继续为点击操作提供服务.而USR1 信号引起主进程处理并警告收到当前请求之后的子进程结束(或在它们不做任何服务时立刻结束). 主进程将重新读取它的配置文件并打开它的日志文件. 当每一个子进程结束后,主进程将使用配置信息产生一个新的子进程来立刻响应请求并为之服务. --好象挺复杂的是吧--主要是知道graceful要比restart 好就行了</p>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/39400.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-01-28 17:09 <a href="http://www.cnitblog.com/jakiegu/archive/2008/01/28/39400.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于编写单元测试中的驱动模块</title><link>http://www.cnitblog.com/jakiegu/archive/2008/01/25/39310.html</link><dc:creator>测试时代-谷峰</dc:creator><author>测试时代-谷峰</author><pubDate>Fri, 25 Jan 2008 03:23:00 GMT</pubDate><guid>http://www.cnitblog.com/jakiegu/archive/2008/01/25/39310.html</guid><wfw:comment>http://www.cnitblog.com/jakiegu/comments/39310.html</wfw:comment><comments>http://www.cnitblog.com/jakiegu/archive/2008/01/25/39310.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jakiegu/comments/commentRss/39310.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jakiegu/services/trackbacks/39310.html</trackback:ping><description><![CDATA[<p>在网上闲逛的是后看到关于<a href="http://www.testage.net/bbs">单元测试</a>的东西，觉得非常不错，拿过来一同分享一下，测试中有一个pareto的原则，想必大家都知道，就是在需求和设计的静态测试中，可以发现80%的bug，但这是一个理想的状态。又有多少公司能够做到全面的需求和设计的静态测试呢，所以有很多公司会把一部分的精力放在单元测试里面，来进一步的靠近pareto的原则，一般来说，单元测试会详细描述，单元模块的划分，以及该模块的接口描述和说明。下面我来贴出来一段详细设计里面所设计到的模块的说明，我想对于有的看不到详细设计的测试工程师会有所帮助！<br></p>
<p>&nbsp;——————————————————————————————————————————————————————————————</p>
<h1><a name=_Toc48537177><span>6</span><span>、系统参数程序设计说明</span></a></h1>
<h2><a name=_Toc48537178><span>6.1</span><span>程序描述</span></a></h2>
<p><span>显示传输系统参数配置界面<em></em></span></p>
<h2><a name=_Toc48537179><span>6.2</span><span>功能</span></a></h2>
<p><span>提供传输子系统系统正常运行所必需的参数。</span></p>
<h2><a name=_Toc48537180><span>6.3</span><span>性能</span></a></h2>
<h2><a name=_Toc48537181><span>6.4</span><span>输入项</span></a></h2>
<p><span>输入数据：</span></p>
<p><span><span>1.<span>&nbsp;&nbsp;&nbsp; </span></span></span><span>写参数配置文件：参数配置属性页；</span></p>
<p><span>2.</span><span>读参数配置文件：参数配置文件。</span></p>
<p><span>输入说明：</span></p>
<p><span>参数文件与系统互动，通过参数配置属性页建立<span>/</span>更新参数配置文件；</span></p>
<p><span>各模块通过调用函数从参数配置文件中取得相应的参数值。</span></p>
<h2><a name=_Toc48537182><span>6.5</span><span>输出项</span></a></h2>
<p><span>输出数据：</span></p>
<p><span><span>1.<span>&nbsp;&nbsp;&nbsp; </span></span></span><span>写参数配置文件：参数配置文件；</span></p>
<p><span>2.</span><span>读参数配置文件：参数配置属性页、各模块所需参数。</span></p>
<p><span>输出说明：</span></p>
<p><span>参数文件与系统互动，将参数配置文件中的参数值回显到参数配置属性页；各模块通过调用函数从参数配置文件中取得相应的参数值；</span></p>
<h2><a name=_Toc48537183><span>6.6</span><span>算法</span></a></h2>
<p><span>参数配置文件位于系统程序的运行目录，名称：<span>Octdts.Properties</span>。参数配置文件涉及到整个系统的正常运行，故参数配置文件的管理异常重要，一般情况下，不允许对参数配置文件直接进行手工修改，须通过程序实现对参数配置文件的更新；为了最大程度上的实现系统的通用性，参数配置文件中所包含的参数很详细，基本函概了系统所需的动态参数；由于设计到安全问题，数据库用户密码不以明文方式记录在参数配置文件中，调用一个加密算法对密码进行加密，在取密码时，需调用一个解密算法。<span><br><br></span></span></p>
<h2><a name=_Toc48537184><span>6.7</span><span>流程逻辑</span></a></h2>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2><a name=_Toc48537185><span>6.8</span><span>接口</span></a></h2>
<p>
<table cellSpacing=0 cellPadding=0 width=583 border=1>
    <tbody>
        <tr>
            <td vAlign=top width=58>
            <p align=center><span>序号</span></p>
            </td>
            <td vAlign=top width=261>
            <p align=center><span>调用者</span></p>
            </td>
            <td vAlign=top width=264>
            <p align=center><span>被调用者</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=58>
            <p><span><span>1.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span>&nbsp;</p>
            </td>
            <td vAlign=top width=261>
            <p><strong><span>类名称</span></strong><span>：<span>FrameMain</span></span></p>
            <p><strong><span>类说明</span></strong><span>：程序主界面</span></p>
            <p><strong><span>调用说明</span></strong><span>：按钮、菜单显示参数配置界面</span></p>
            </td>
            <td vAlign=top width=264>
            <p><strong><span>名称：</span></strong><span>本类的实例化对象 <span>ParameterPage</span></span></p>
            <p><strong><span>调用说明：</span></strong><span>show()</span><span>、<span>toFront</span>（）方法</span></p>
            <p><span>。</span></p>
            </td>
        </tr>
        <tr>
            <td vAlign=top width=58>
            <p><span><span>2.<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span>&nbsp;</p>
            </td>
            <td vAlign=top width=261>
            <p><span>各个服务模块</span></p>
            </td>
            <td vAlign=top width=264>
            <p><strong><span>调用说明：</span></strong><span>直接调用<span>getParam</span>（）方法<strong></strong></span></p>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>——————————————————————————————————————————————————————————<br></p>
<p>那么对于这一个单元模块来讲，在开发时是再涉及到单元测试时，往往show（），toFront（）方法还没有开发出来，所以测试工程师需要独立开发驱动模块或桩模块。，驱动模块和桩模块的概念如下，网上有很多概念，不好理解，我认为用这种说法更方便理解<br><br></p>
<p><span style="COLOR: red">驱动模块在大多数场合称为"主程序"，它接收测试数据并将这些数据传递到被测试模块，相反桩模块就是那些被测试模块所调用的&#8220;程序&#8221;，它们之间的区别是调用与被调用的关系<br></span></p>
<p><font style="COLOR: #000000" color=#ff0000>往往被测试单元的上层程序还没有开发出来我们就需要实施单元测试，上层程序有时要包含多个调用界面，函数，方法等，而我们测试的单元模块的期望结果有可能只有返回一个值或是传递一个参数，这时候就需要编写驱动模块或者是桩模块（如果是需要调用其它函数返回的值）。<br></font></p>
<p>如何编写桩模块？举一个简单的例子<br></p>
<p>/*被测程序*/<br>int Fun(int in)<br>{<br>&nbsp; &nbsp; if (in &gt;= 0)<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return 1;<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; else<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return -1;<br>&nbsp; &nbsp; }<br>}<br><br><br><br>那么通过TCL进行扩展指令编写时，针对该被测函数，驱动如下：<br><br>/*用户自己扩展的用户指令，用来驱动被测函数*/<br>int Ex_TestFun(ClientData clientData,Tcl_Interp * interp,int argc,&nbsp;&nbsp;char* argv[])<br>{<br>&nbsp; &nbsp;&nbsp;&nbsp;int i;<br>&nbsp; &nbsp;&nbsp;&nbsp;int ret，iExceptedRet;<br><br>&nbsp; &nbsp; //打开测试结果记录文件<br>&nbsp; &nbsp; FILE * out;<br>&nbsp; &nbsp; out = fopen("D:\\result.txt","a");<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;<br>&nbsp; &nbsp; //第一步：检查用户输入参数个数是否正确<br>&nbsp; &nbsp; if (3 != argc)<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; fputs("Parameters error",out);<br>&nbsp; &nbsp; &nbsp; &nbsp; fflush(out);<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; return TCL_ERROR;<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; <br>&nbsp; &nbsp;//第二步：取出用户输入参数<br>&nbsp; &nbsp; if (TCL_OK != Tcl_GetInt(interp,argv[1],&amp;i))<br>&nbsp; &nbsp;{<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return TCL_ERROR;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;<br>&nbsp; &nbsp;}<br><br>&nbsp; &nbsp;if (TCL_OK != Tcl_GetInt(interp,argv[2],&amp;iExceptedRet))<br>&nbsp; &nbsp;{<br>&nbsp; &nbsp; &nbsp; &nbsp; return TCL_ERROR;<br>&nbsp; &nbsp;}<br><br><br>&nbsp; &nbsp;//第三步：将参数传递给被测函数<br>&nbsp; &nbsp;ret = Fun(i);<br><br>&nbsp; &nbsp;//第四步：将被测函数执行结果和输入的期望结果进行比较，根据比较结果作为用例执行结果输出到测试报告中<br>&nbsp;&nbsp;if (ret != iExceptedRet)<br>&nbsp; &nbsp;{<br>&nbsp; &nbsp; &nbsp; &nbsp; fputs("test fail",out);<br>&nbsp; &nbsp; &nbsp; &nbsp; fflush(out);<br>&nbsp; &nbsp;}<br>&nbsp; &nbsp;else <br>&nbsp; &nbsp;{<br>&nbsp; &nbsp; &nbsp; &nbsp; fputs("test success",out);<br>&nbsp; &nbsp; &nbsp; &nbsp; fflush(out);<br>&nbsp; &nbsp;}<br><br>&nbsp; &nbsp;return TCL_OK;<br>}<br>——————————————————————————<br></p>
<p>驱动模块编写完毕后，就可以直接实施单元测试而不必等待上层的程序开发出来才能进行测试，提高工作效率</p>
<img src ="http://www.cnitblog.com/jakiegu/aggbug/39310.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jakiegu/" target="_blank">测试时代-谷峰</a> 2008-01-25 11:23 <a href="http://www.cnitblog.com/jakiegu/archive/2008/01/25/39310.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>测试时代-谷峰</dc:creator><author>测试时代-谷峰</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">测试时代-谷峰</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>