﻿<?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/category/6704.html</link><description>测试时代的gufeng</description><language>zh-cn</language><lastBuildDate>Sun, 17 Feb 2008 05:19:12 GMT</lastBuildDate><pubDate>Sun, 17 Feb 2008 05:19:12 GMT</pubDate><ttl>60</ttl><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>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>