﻿<?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>BlogJava-西瓜地儿-随笔分类-Junit</title><link>http://www.blogjava.net/ashutc/category/46477.html</link><description>j2ee</description><language>zh-cn</language><lastBuildDate>Wed, 29 Sep 2010 22:33:43 GMT</lastBuildDate><pubDate>Wed, 29 Sep 2010 22:33:43 GMT</pubDate><ttl>60</ttl><item><title>jWebUnit 框架 测试用例</title><link>http://www.blogjava.net/ashutc/archive/2010/09/29/333401.html</link><dc:creator>西瓜</dc:creator><author>西瓜</author><pubDate>Wed, 29 Sep 2010 07:40:00 GMT</pubDate><guid>http://www.blogjava.net/ashutc/archive/2010/09/29/333401.html</guid><wfw:comment>http://www.blogjava.net/ashutc/comments/333401.html</wfw:comment><comments>http://www.blogjava.net/ashutc/archive/2010/09/29/333401.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ashutc/comments/commentRss/333401.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ashutc/services/trackbacks/333401.html</trackback:ping><description><![CDATA[<span style="color: #060000;"><span style="color: #060000;"><span style="color: #060000;">
<p>自动测试可以节省重复执行相同测试步骤的
时间和精力。本文将介绍 jWebUnit，这是一组 Java 类，用于为 Web 应用程序开发测试用例。jWebUnit 是一个开源项目，可以在
BSD 许可下免费获得。我将介绍如何下载 jWebUnit 库、配置 Eclipse 平台来开发 jWebUnit
测试用例，以及如何构建一个示例测试用例。</p>
<p><a name="2"><span class="atitle2">jWebUnit 简介</span></a><br />
jWebUnit 以 HttpUnit（一个进行 Web 应用程序自动测试的 Java 库）和 JUnit 单元测试框架为基础（请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jwebunit/#resources">参考资料</a>）。
jWebUnit 提供了导航 Web 应用程序的高级
API，并组合了一组断言，用它们来验证链接导航、表单输入项和提交、表格内容以及其他典型商务 Web 应用程序特性的正确性。 jWebUnit 以
JAR 文件形式提供的，可以很容易地将它插入大多数 IDE 中，jWebUnit 也包含其他必要的库。</p>
<p><a name="2"><span class="atitle3">用 HttpUnit 进行测试</span></a><br />
对
Web 应用程序自动进行测试意味着跳过 Web 浏览器，通过程序来处理 Web 站点。首先，我要介绍 HttpUnit（JWebUnit
的构建块之一）是如何简化这项工作的。HttpUnit 可以模拟帧、JavaScript、页面重定向 cookie，等等。在将 HttpUnit
用于 JUnit 时，它可以迅速地对 Web 站点的功能进行验证。</p>
<p>清单 1 显示了一个用 HttpUnit 编写的测试用例，它试图单击 HttpUnit 主页上的&#8220;Cookbook&#8221;链接：</p>
<a name="listing1"><strong>清单 1. 单击 HttpUnit 主页上 Cookbook 链接的 HttpUnit 代码</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="600">
    <tbody>
        <tr>
            <td>
            <pre><code>
            1 public class HttpUnitTest {
            2  public static void main(String[] args) {
            3    try {
            4      WebConversation wc = new WebConversation();
            5      WebRequest request =
            new GetMethodWebRequest("http://httpunit.sourceforge.net/index.html");
            6      wc.setProxyServer( "your.proxy.com", 80 );
            7      WebResponse response = wc.getResponse(request);
            8      WebLink httpunitLink =
            response.getFirstMatchingLink(WebLink.MATCH_CONTAINED_TEXT,"Cookbook");
            9      response = httpunitLink.click();
            10      System.out.println("Test successful !!");
            11    } catch (Exception e) {
            12      System.err.println("Exception: " + e);
            13    }
            14  }
            15 }
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>清单 1 中的代码用 <code>your.proxy.com</code> （第 6 行）连接 Internet。如果存在直接 Internet 连接，那么可以把这个语句注释掉。第 8 行的语句在页面中搜索包含文本 <em>Cookbook</em> 的 Web 链接。第 9 行的语句用于单击这个链接。如果找到链接，那么用户会看到 <code>Test Successful !!</code> 这条消息。</p>
<p><a name="3"><span class="atitle3">用 jWebUnit 进行的测试更简单</span></a><br />
清单 2 的测试用例用 jWebUnit API 执行和清单 1 相同的任务：</p>
<a name="listing2"><strong>清单 2. 单击 HttpUnit 主页上 Cookbook 链接的 jWebUnit 代码</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            1 public class JWebUnitTest extends WebTestCase{
            2  public static void main(String[] args){
            3    junit.textui.TestRunner.run(new TestSuite(JWebUnitTest.class));
            4  }
            5  public void setUp(){
            6    getTestContext().setBaseUrl("http://httpunit.sourceforge.net");
            7    getTestContext().setProxyName("webproxy.watson.ibm.com");
            8    getTestContext().setProxyPort(8080);
            9  }
            10  public void testSearch(){
            11    beginAt("/index.html");
            12    clickLinkWithText("Cookbook");
            13  }
            14 }
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>如果没注意清单 2 中特定于 JUnit 的代码，那么您可以看到，测试用例现在变得相当整洁、简练。需要查看的重要的行是第 6 行、第 11 行和第 12 行。在第 6 行，基本 URL 被设置到 HttpUnit 的主页中。第 11 行用相对路径 <code>/index.html</code> 连接站点。第 12 行用于单击页面上具有文本 <em>Cookbook</em> 的链接。如果链接有效，那么 JUnit 会报告成功；否则，JUnit 会报告异常。</p>
<p><a name="4"><span class="atitle2">jWebUnit API：进一步观察</span></a><br />
每个 jWebUnit 测试的核心都是 <code>net.sourceforge.jwebunit.WebTestCase</code> 类，它代表测试用例。每个测试用例都必须是从这个类扩展而来。（<code>net.sourceforge.jwebunit.WebTestCase</code> 类本身则是从 <code>junit.framework.TestCase</code> 类扩展而来的，它在 JUnit 中代表测试用例。） 表 1 描述了这个类的一些常用方法：</p>
<a name="IDA2UMX"><strong>表 1. net.sourceforge.jwebunit.WebTestCase 类的重要方法</strong></a><br />
<table border="1" cellpadding="3" cellspacing="1" width="100%">
    <tbody>
        <tr valign="top">
            <td><strong>方法</strong></td>
            <td><strong>说明</strong></td>
        </tr>
        <tr valign="top">
            <td>public TestContext getTestContext()</td>
            <td>得到测试用例的上下文。可以用它访问像地区、基本 URL 和 cookie 之类的项目</td>
        </tr>
        <tr valign="top">
            <td>public void beginAt(String relativeURL)</td>
            <td>在相对于基本 URL 的 URL 处开始对话</td>
        </tr>
        <tr valign="top">
            <td>public void setWorkingForm(String nameOrId)</td>
            <td>与指定的表单开始交互。如果当前页面只有一个表单，就不需要调用这个方法</td>
        </tr>
        <tr valign="top">
            <td>protected void submit()</td>
            <td>提交表单 —— 等同于单击表单的 <strong>提交</strong> 按钮</td>
        </tr>
        <tr valign="top">
            <td>public void gotoFrame(String frameName)</td>
            <td>激活命名帧</td>
        </tr>
    </tbody>
</table>
<p><span style="color: #060000;">另一个重要的类是 <code>net.sourceforge.jwebunit.TestContext</code>。它为测试创建上下文。可以用这个类来处理像 cookie、会话和授权之类的信息。表 2 显示了这个类的一些重要方法：</span></p>
<a name="IDAJWMX"><strong>表 2. net.sourceforge.jwebunit.TestContext 类的重要方法</strong></a><br />
<table border="1" cellpadding="3" cellspacing="1" width="100%">
    <tbody>
        <tr valign="top">
            <td><strong>方法</strong></td>
            <td><strong>说明</strong></td>
        </tr>
        <tr valign="top">
            <td>public void addCookie(String name, String value)</td>
            <td>向测试上下文中添加 cookie。在 <code>HttpUnitDialog</code> 开始时，添加的 cookie 被设置到 <code>WebConversation</code> 上</td>
        </tr>
        <tr valign="top">
            <td>public void setResourceBundleName(String name)</td>
            <td>为测试上下文设置一个使用的资源绑定。用于按照 <code>WebTester</code> 中的键查找期望的值</td>
        </tr>
        <tr valign="top">
            <td>public void setProxyName(String proxyName)</td>
            <td>为测试上下文设置代理服务器名称</td>
        </tr>
        <tr valign="top">
            <td>public void setBaseUrl(String url)</td>
            <td>为测试上下文设置基本 URL</td>
        </tr>
    </tbody>
</table>
<p><a name="5"><span class="atitle2">下载 jWebUnit，在 Eclipse 中配置 jWebUnit</span></a><br />
jWebUnit 是用纯 Java 代码实现的，所以可以以 JAR 文件的形式获得它 （请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jwebunit/#resources">参考资料</a>，从中获得下载链接）。在完成下载之后，请按以下步骤在 Eclipse 平台上配置 jWebUnit 库：</p>
<ol>
    <li>把下载的文件 jwebunit-1.2.zip 释放到临时目录中（假设是 C:\temp）。</li>
    <li>在 Eclipse 中创建新 Java 项目，将其命名为 jWebUnit。</li>
    <li>右击 Package Explorer 视图中的 jWebUnit 项目，然后选择 <strong>Properties</strong>。</li>
    <li>单击 <strong>Java Build Path</strong>。单击 Libraries 标签中的 <strong>Add External JARs</strong>。</li>
    <li>浏览到 C:\temp\jwebunit-1.2\lib 目录，选择这个目录中的所有 JAR 文件。</li>
    <li>单击 <strong>OK</strong>。</li>
</ol>
<p>现在可以在 Eclipse 中的 jWebUnit 项目下开发 jWebUnit 测试用例了。 </p>
<p><a name="6"><span class="atitle2">构建示例应用程序</span></a><br />
现在就可以查看 jWebUnit API 的实际应用了。我将带您研究一个示例应用程序，帮助您更好地理解 jWebUnit 的真正威力。这个应用程序是一个测试用例，用于打开一个 Google 搜索页面并搜索文本 <em>HttpUnit</em>。应用程序需要测试以下场景：</p>
<ul>
    <li>打开 Google 主页 http://www.google.com。<br />
    <br />
    </li>
    <li>确定该页包含一个名为 <code>q</code> 的表单元素。（在 Google 的主页上，名为 <code>q</code> 的文本框是接受用户查询输入的文本框。）应用程序用这个元素输入搜索参数。<br />
    <br />
    </li>
    <li>在搜索文本框中输入字符串 <code>HttpUnit Home</code>，并提交表单。<br />
    <br />
    </li>
    <li>获得结果页，并确定该页面包含的链接中包含文本 <em>HttpUnit Home</em>。<br />
    <br />
    </li>
    <li>单击包含文本 <em>HttpUnit Home</em> 的链接。</li>
</ul>
<p>现在测试场景已经就绪，可以编写 Java 应用程序，用 jWebUnit 实现这些需求了。</p>
<p>第一步是声明一个从 <code>WebTestCase</code> 扩展而来的类，如清单 3 所示：</p>
<a name="listing3"><strong>清单 3. 声明测试用例类</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            public class GoogleTest extends WebTestCase {
            static String searchLink = "";
            }
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>正如我在前面提到过的，jWebUnit 要求每个测试用例都是从 <code>WebTestCase</code> 中扩展而来的。<code>searchLink</code> 保存传入的搜索参数。这个值以命令行参数的形式传递给测试用例。</p>
<p>下一步是声明入口点 —— <code>main()</code> 方法，如清单 4 所示： </p>
<a name="listing4"><strong>清单 4. <code>main()</code> 方法</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            public static void main(String[] args) {
            searchLink = args[0];
            junit.textui.TestRunner.run(new TestSuite(GoogleTest.class));
            }
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p><code>main()</code> 方法调用 <code>junit.textui.TestRunner.run()</code> 执行 <code>JTest</code> 测试用例。因为需要运行 <code>GoogleTest</code> 测试用例，所以，作为参数传递给 <code>run()</code> 方法的测试套件采用 <code>GoogleTest.class</code> 作为参数。</p>
<p>接下来，<code>JTest</code> 调用 <code>setUp()</code> 方法来设置基本 URL 和代理，如清单 5 所示：</p>
<a name="listing5"><strong>清单 5. 设置</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            public void setUp() {
            getTestContext().setBaseUrl("http://www.google.com");
            getTestContext().setProxyName("proxy.host.com");
            getTestContext().setProxyPort(80);
            }
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>清单 5 把基本 URL 设置为 <code>http://www.google.com</code>。这意味着测试用例的启动是相对于这个 URL 的。下面两个语句设置连接到 Internet 的代理主机和代理端口。如果是直接连接到 Internet，那么可以忽略代理设置语句。</p>
<p>现在开始浏览站点并输入搜索参数。清单 6 显示了访问 Web 页面，然后测试所有场景的代码：</p>
<a name="listing6"><strong>清单 6. 测试所有场景</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            public void testSearch() {
            beginAt("/");
            assertFormElementPresent("q");
            setFormElement("q", "HttpUnit");
            submit("btnG");
            assertLinkPresentWithText(searchLink);
            clickLinkWithText(searchLink);
            }
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>清单 6 的代码连接到基本 URL，并相对于 <code>/</code> 开始浏览。然后它断定页面中包含一个名为 <code>q</code> 的表单元素 —— q 是 Google 主页上查询输入文本框的名称。下一条语句用值 <code>HttpUnit</code> 设置名为 <code>q</code> 的文本框。再下一条语言提交表单上名为 <code>btnG</code> 的提交按钮。（在 Google 的主页上，名为 <code>btnG</code> 的按钮是标签为 <strong>Google Search</strong> 的按钮。）表单是在这个对话中提交的，下一页列出搜索结果。在结果页面上，代码首先检查是否有一个链接的文本是 <em>HttpUnit Home</em>。如果该链接不存在，那么测试就以 <code>AssertionFailedError</code> 失败。如果该链接存在，则测试执行的下一个操作是单击链接。 </p>
<p><a name="7"><span class="atitle2">运行示例应用程序</span></a><br />
现在把示例应用程序投入使用当中：</p>
<ol>
    <li>下载示例应用程序 j-webunitsample.jar （请参阅 <a href="http://www-128.ibm.com/developerworks/cn/java/j-jwebunit/#download">下载</a>）。<br />
    <br />
    </li>
    <li>在一个目录中解压缩 j-webunitsample.jar。例如，如果把它释放到 C:\temp 中，那么就要把源文件和类文件放在 C:\temp\com\jweb\test 中，而 setclasspth.bat 则放在 C:\temp 中。<br />
    <br />
    </li>
    <li>编辑 setclasspath.bat：设置 <code>JAR_BASE</code> 指向包含所有必需 JAR 文件的目录。例如，如果在 C:\temp 中释放 jwebunit-1.2.zip 文件，那么将 JAR_BASE 设置为 C:\temp\jwebunit-1.2\lib。<br />
    <br />
    </li>
    <li>打开命令行提示符，切换到 C:\temp 目录。<br />
    <br />
    </li>
    <li>执行 <code>setclasspath.bat</code>。这会设置执行测试用例所需的 <code>CLASSPATH</code>。<br />
    <br />
    </li>
    <li>用命令 <code>java com.jweb.test.GoogleTest "HttpUnit Home"</code> 运行应用程序。</li>
</ol>
<p><a name="6.2"><span class="atitle3">示例输出</span></a><br />
在执行了测试用例之后，会在命令行输出一个测试用例报告。如果测试失败，报告看起来如清单 7 中所示：</p>
<a name="listing7"><strong>清单 7. 带有断言失败的输出</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            C:\temp&gt;java com.jweb.test.GoogleTest "HttpUnit Hwee"
            .F
            Time: 5.338
            There was 1 failure:
            1) testSearch(com.jweb.test.GoogleTest)junit.framework.AssertionFailedError: Link
            with text [HttpUnit Hwee] not found in response.
            at net.sourceforge.jwebunit.WebTester.assertLinkPresentWithText(WebTester.java:618)
            at net.sourceforge.jwebunit.WebTestCase.assertLinkPresentWithText(WebTestCase.java:244)
            at com.jweb.test.GoogleTest.testSearch(GoogleTest.java:36)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
            at com.jweb.test.GoogleTest.main(GoogleTest.java:19)
            FAILURES!!!
            Tests run: 1,  Failures: 1,  Errors: 0
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p>正如在清单 7 中可以看到的，可以用 <code>HttpUnit Hwee</code> 作为参数来执行测试用例。这个测试用例遇到断言的地方会失败，因为结果页面中不包含带有这个文本的链接。由此也就产生了<code>junit.framework.AssertionFailedError</code>。</p>
<p>清单 8 执行时用 <code>HttpUnit Home</code> 作为参数。测试用例找到一个带有这个文本的链接，所以测试通过了：</p>
<a name="listing8"><strong>清单 8. 成功测试的输出</strong></a><br />
<table bgcolor="#cccccc" border="1" cellpadding="5" cellspacing="0" width="100%">
    <tbody>
        <tr>
            <td>
            <pre><code>
            C:\temp&gt;java com.jweb.test.GoogleTest "HttpUnit Home"
            .
            Time: 6.991
            OK (1 test)
            </code></pre>
            </td>
        </tr>
    </tbody>
</table>
<p><a name="8"></a><a name="8"><span class="atitle2">结束语</span></a><br />
本
文通过讨论 jWebUnit 框架的一些突出特性和最重要的类，介绍如何用它创建简洁的测试用例，让您对 jWebUnit
框架有一个认识。jWebUnit 还有更多特性可以用在测试用例中。它支持测试 Web
页面中的链接行数。可以对字符串、表或者带有指定标签的表单输入元素是否存在于页面上进行断言。此外，jWebUnit 还可以处理 cookie
（例如断言存在某个 cookie、删除 cookie 等。）测试可以对某个文本之后出现的特定文本的链接进行单击。 如果想为 Web
应用程序构建快而有效的测试用例，jWebUnit 可能是您最好的朋友。</p>
</span><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</span></span>
<img src ="http://www.blogjava.net/ashutc/aggbug/333401.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ashutc/" target="_blank">西瓜</a> 2010-09-29 15:40 <a href="http://www.blogjava.net/ashutc/archive/2010/09/29/333401.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JUnit in java</title><link>http://www.blogjava.net/ashutc/archive/2010/09/29/333382.html</link><dc:creator>西瓜</dc:creator><author>西瓜</author><pubDate>Wed, 29 Sep 2010 05:30:00 GMT</pubDate><guid>http://www.blogjava.net/ashutc/archive/2010/09/29/333382.html</guid><wfw:comment>http://www.blogjava.net/ashutc/comments/333382.html</wfw:comment><comments>http://www.blogjava.net/ashutc/archive/2010/09/29/333382.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ashutc/comments/commentRss/333382.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ashutc/services/trackbacks/333382.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: v\:* {behavior:url(#default#VML);}o\:* {behavior:url(#default#VML);}w\:* {behavior:url(#default#VML);}.shape {behavior:url(#default#VML);}Normal07.8 磅02falsefalsefa...&nbsp;&nbsp;<a href='http://www.blogjava.net/ashutc/archive/2010/09/29/333382.html'>阅读全文</a><img src ="http://www.blogjava.net/ashutc/aggbug/333382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ashutc/" target="_blank">西瓜</a> 2010-09-29 13:30 <a href="http://www.blogjava.net/ashutc/archive/2010/09/29/333382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>spring 测试用例</title><link>http://www.blogjava.net/ashutc/archive/2010/09/29/333349.html</link><dc:creator>西瓜</dc:creator><author>西瓜</author><pubDate>Wed, 29 Sep 2010 05:25:00 GMT</pubDate><guid>http://www.blogjava.net/ashutc/archive/2010/09/29/333349.html</guid><wfw:comment>http://www.blogjava.net/ashutc/comments/333349.html</wfw:comment><comments>http://www.blogjava.net/ashutc/archive/2010/09/29/333349.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ashutc/comments/commentRss/333349.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ashutc/services/trackbacks/333349.html</trackback:ping><description><![CDATA[<br />
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><strong><span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">篇首语</span></strong><strong><span style="font-size: 12pt; line-height: 150%;" lang="EN-US"> </span></strong></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span></font><font size="2"><span style="font-family: 宋体;">本文假设读者已经熟悉单元测试及</span><span lang="EN-US"><font>JUnit</font></span><span style="font-family: 宋体;">工具的使用，如果对单元测试及</span><span lang="EN-US"><font>JUnit</font></span><span style="font-family: 宋体;">尚不了解请先学习单元测试及</span><span lang="EN-US"><font>JUnit</font></span><span style="font-family: 宋体;">工具的相关知识。读者最好对</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">框架及</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">框架提供的单元测试支持有所了解，因为本文案例基于</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">技术编写。但对</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">不了解并不影响本文所讲述的单元测试用例编写及回调模式、模板方法的应用。</span></font></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span></font><font size="2"><span style="font-family: 宋体;">单元测试是编写高质量代码的前提，通过编写有效的单元测试即可以保证代码的质量又可以提高开发速度，因为大多数问题都可以通过单元测试发现并解决而不需要部署到应用服务器。纵览网上流行的优秀开源框架，无一不提供完整的单元测试用例。</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">框架便是其中的代表和佼佼者，因为</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">所遵循的控制反转（</span><span lang="EN-US"><font>IoC</font></span><span style="font-family: 宋体;">）和依赖注入（</span><span lang="EN-US"><font>DI</font></span><span style="font-family: 宋体;">）原则使编写有效、干净的单元测试用例变得更加方便、快捷。</span></font></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font><strong><span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">编写单元测试用例</span></strong><strong><span style="font-size: 12pt; line-height: 150%;" lang="EN-US"> </span></strong></p>
</span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span style="font-family: 宋体;">本文所采用的案例非常简单，就是对数据库表的增、删、改、查操作进行测试。假设我们有这样一个表</span><span lang="EN-US"><font>url</font></span><span style="font-family: 宋体;">（</span><span lang="EN-US"><font>MySql</font></span><span style="font-family: 宋体;">数据库）：</span></font></p>
<p>
</p>
<table class="MsoTableGrid" style="border: medium none; border-collapse: collapse;" border="1" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td style="padding: 0cm 5.4pt; background: none repeat scroll 0% 50% #cccccc; width: 85.2pt;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><strong><font size="3"><span style="font-family: 宋体;">字段</span><span lang="EN-US">
            <p>&nbsp;</p>
            </span></font></strong></p>
            <p>&nbsp;</p>
            </td>
            <td style="padding: 0cm 5.4pt; background: none repeat scroll 0% 50% #cccccc; width: 85.2pt;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><strong><font size="3"><span style="font-family: 宋体;">类型</span><span lang="EN-US">
            <p>&nbsp;</p>
            </span></font></strong></p>
            <p>&nbsp;</p>
            </td>
            <td style="padding: 0cm 5.4pt; background: none repeat scroll 0% 50% #cccccc; width: 249pt;" valign="top" width="332">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><strong><font size="3"><span style="font-family: 宋体;">描述</span><span lang="EN-US">
            <p>&nbsp;</p>
            </span></font></strong></p>
            <p>&nbsp;</p>
            </td>
        </tr>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">id</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">int</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 249pt; background-color: transparent;" valign="top" width="332">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">主键，自增类型</font></span></p>
            </td>
        </tr>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">url</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">varchar</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 249pt; background-color: transparent;" valign="top" width="332">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">网站地址，唯一不能重复</font></span></p>
            </td>
        </tr>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">email</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">varchar</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 249pt; background-color: transparent;" valign="top" width="332">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><font>Email</font></span><span style="font-family: 宋体;">地址</span></font></p>
            </td>
        </tr>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">name</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 85.2pt; background-color: transparent;" valign="top" width="114">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%; text-align: center;" align="center"><span lang="EN-US"><font size="3">varchar</font></span></p>
            </td>
            <td style="padding: 0cm 5.4pt; width: 249pt; background-color: transparent;" valign="top" width="332">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">名称</font></span></p>
            </td>
        </tr>
    </tbody>
</table>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">正如你所见，该表只有几个字段，但对于我们的案例来说完全够用。</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font></p>
</span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span style="font-family: 宋体;">看到此处，你应该清楚我们是要对数据库操作进行单元测试。如果你是一位经验丰富的开发人员，此时已经会有许多疑问，甚至已经失去继续阅读本文的兴趣：</span></font></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><span style="font-family: Wingdings;" lang="EN-US"><span><font size="3">&#178;</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span style="font-family: 宋体;"><font size="3">单元测试不应该直接操作数据库？</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><span style="font-family: Wingdings;" lang="EN-US"><span><font size="3">&#178;</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><font size="3"><span style="font-family: 宋体;">对数据库操作的单元测试可以采用</span><span lang="EN-US"><font>DAO</font></span><span style="font-family: 宋体;">模式，</span><span lang="EN-US"><font>Mock</font></span><span style="font-family: 宋体;">一个实现类？</span></font></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><span style="font-family: Wingdings;" lang="EN-US"><span><font size="3">&#178;</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span style="font-family: 宋体;"><font size="3">使用内存数据库？</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><span style="font-family: Wingdings;" lang="EN-US"><span><font size="3">&#178;</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><span style="font-family: 宋体;"><font size="3">其他？</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font></p>
</span></p>
<p>&nbsp;</p>
<br />
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt; line-height: 150%;"><font size="3"><span style="font-family: 宋体;">数据库表有了，我们接下来编写</span><span lang="EN-US"><font>DAO</font></span><span style="font-family: 宋体;">及其实现类：</span></font></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font></p>
</span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span><font>DAO</font>接口：</span><span lang="EN-US">
<p>&nbsp;</p>
</span></font></p>
<p>&nbsp;</p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* @author tao.youzt
*/
public interface BizUrlDAO {
public Object insert(BizUrlDO bizUrlDO);
public int delete(String url);
public BizUrlDO getByUrl(String url);
}</pre>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font></p>
</span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span><font>DAO</font>实现类，该类继承一个支持类，封装了对数据库的操作。</span></font></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* @author tao.youzt
*/
public class BizUrlIbatisImpl extends GodzillaDaoSupport implements BizUrlDAO {
private static final String GET_BY_URL = "Select-BIZ-URL";
private static final String Delete     = "Delete-BIZ-URL";
private static final String Insert     = "Insert-BIZ-URL";
public int delete(String url) {
return this.delete(Delete, url);
}
public BizUrlDO getByUrl(String url) {
return this.queryForObject(GET_BY_URL, url, BizUrlDO.class);
}
public Object insert(BizUrlDO bizUrlDO) {
return this.insert(Insert, bizUrlDO);
}
}</pre>
<p><font size="3"><span><font>DO</font>领域对象</span><span lang="EN-US">
<p>&nbsp;</p>
</span></font></p>
<p>&nbsp;</p>
<p><font size="3"></font></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* @author tao.youzt
*/
public class BizUrlDO {
private int    id;
private String url;
private String email;
private String name;
// getter and setter
}</pre>
<p><font size="3"><span style="font-family: 宋体;">因为本文案例使用</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">作为底层框架，因此这里需要编写</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">配置文件对</span><span lang="EN-US"><font>DAO</font></span><span style="font-family: 宋体;">进行组装。</span></font></p>
<font size="3"><span style="font-family: 宋体;">
<p><font size="3"><span lang="EN-US"><font>DAO</font></span><span style="font-family: 宋体;">及其配置文件都已经准备完毕，我们接下来编写测试用例。</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">为单元测试提供了很多有用的支持类，我们在这里使用的是：</span></font></p>
<p>
</p>
<table class="MsoTableGrid" style="border: medium none; border-collapse: collapse;" border="1" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td style="padding: 0cm 5.4pt; width: 426.1pt; background-color: transparent;" valign="top" width="568">
            <p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><span style="font-size: 9pt; background: none repeat scroll 0% 50% silver; color: black; line-height: 150%;" lang="EN-US">org.springframework.test.AbstractDependencyInjectionSpringContextTests</span><span style="font-size: 9pt; line-height: 150%;" lang="EN-US">
            <p>&nbsp;</p>
            </span></p>
            <p>&nbsp;</p>
            </td>
        </tr>
    </tbody>
</table>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt; line-height: 150%;"><font size="3"><span style="font-family: 宋体;">该类提供了</span><span lang="EN-US"><font>POJO</font></span><span style="font-family: 宋体;">属性自动注入的能力，只要为为你的属性字段提供一个</span><span lang="EN-US"><font>Set</font></span><span style="font-family: 宋体;">方法即可。下面我们来看完整的测试用例：</span></font></p>
<p><font size="3"></font></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* @author tao.youzt
*/
public class TestBizUrlDAO extends AbstractDependencyInjectionSpringContextTests {
private BizUrlDAO bizUrlDAO;
@Override
protected String[] getConfigLocations() {
return new String[]{"godzilla-dao.xml","godzilla-db.xml"};
}
public void testInsert(){
bizUrlDAO.insert(generateDO());
assertNotNull(bizUrlDAO.getByUrl("www.easyjf.com"));
}
public void testDuplicateInsert(){
bizUrlDAO.insert(generateDO());
try{
bizUrlDAO.insert(generateDO());
assertFalse("Must throw an exception!",true);
}catch(Exception e){
assertTrue(true);
}
}
public void testDelete(){
bizUrlDAO.insert(generateDO());
assertNotNull(bizUrlDAO.getByUrl("www.easyjf.com"));
bizUrlDAO.delete("www.easyjf.com");
assertNull(bizUrlDAO.getByUrl("www.easyjf.com"));
}
private BizUrlSynchronizeDO generateDO() {
BizUrlDO bizUrlDO = new BizUrlDO();
bizUrlDO.setUrl("www.easyjf.com");
bizUrlDO.setName("EasyJWeb");
bizUrlDO.setEmail("webmaster@easyjf.com");
return bizUrlDO;
}
public void setBizUrlDAO(BizUrlSynchronzieDAO bizUrlDAO) {
this.bizUrlDAO = bizUrlDAO;
}
}</pre>
<p><font size="3"><span style="color: black; line-height: 150%;" lang="EN-US">getConfigLocations()</span><span style="color: black; line-height: 150%; font-family: 宋体;">方法为</span></font><span style="font-size: 9pt; background: none repeat scroll 0% 50% silver; color: black; line-height: 150%;" lang="EN-US">AbstractDependencyInjectionSpringContextTests</span><font size="3"><span style="color: black; line-height: 150%;" lang="EN-US"> </span><span style="color: black; line-height: 150%; font-family: 宋体;">提供配置，</span><span style="color: black; line-height: 150%;" lang="EN-US">Spring</span><span style="color: black; line-height: 150%; font-family: 宋体;">会根据该配置文件自动注入</span><span style="color: #0000c0; line-height: 150%;" lang="EN-US">bizUrlDAO</span><span style="line-height: 150%; font-family: 宋体;">属性。</span><span style="color: black; line-height: 150%;" lang="EN-US">testInsert</span><span style="color: black; line-height: 150%; font-family: 宋体;">（）方法用于测试插入新数据，注意这里有个问题，如果数据库中已经存在该</span><span style="color: black; line-height: 150%;" lang="EN-US">URL</span><span style="color: black; line-height: 150%; font-family: 宋体;">的记录，则应用会报错，所以这里还要进行数据清除准备处理，我们称之为&#8220;测试环境准备&#8221;，以后会用到该名词；</span><span style="background: none repeat scroll 0% 50% silver; color: black; line-height: 150%;" lang="EN-US">testDuplicateInsert</span><span style="color: black; line-height: 150%; font-family: 宋体;">（）方法用于测试插入重复数据的情况，该方法同样存在上面的问题；</span><span style="color: black; line-height: 150%;" lang="EN-US">testDelete()</span><span>方法用于测试删除数据的情况，这里尽管准备了数据，但仍没有考虑数据库中已经有记录的情况。 </span></font><span style="color: black; line-height: 150%;" lang="EN-US">
</span>
</p>
<p>&nbsp;</p>
<font size="3"><span style="color: black; line-height: 150%;" lang="EN-US"><span>&nbsp;&nbsp;&nbsp; </span></span><span>综上所述，尽管该测试类已经比较清晰，但仍然存在许多不足之处。我们将在后面的章节进行详细分析，并给出解决方案。 </span></font>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><strong><span style="font-size: 12pt; line-height: 150%;" lang="EN-US"><font>Callback Function &amp; Template Method <span>&nbsp;</span>Pattern </font></span></strong>
</p>
<p>&nbsp;</p>
<font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span style="font-family: 宋体;">回调函数（</span><span lang="EN-US"><font>Callback Function</font></span><span style="font-family: 宋体;">）和模板方法（</span><span lang="EN-US"><font>Template Method</font></span><span style="font-family: 宋体;">）是软件架构设计中最常用的两种设计模式，这两种设计模式在</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">框架中随处可见。</span></font>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span style="font-family: 宋体;">关于本节是否要详细介绍回调函数（</span><span lang="EN-US"><font>Callback Function</font></span><span style="font-family: 宋体;">）和模板方法（</span><span lang="EN-US"><font>Template Method</font></span><span style="font-family: 宋体;">）模式的问题，笔者考虑了很长时间。因为网络上对这两种普遍使用的设计模式的定义层出不穷，各有各的道理，很难说谁是谁非。况且，针对不同的应用场景，这两种模式也有许多变体，或者联合使用。</span></font></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">因此，笔者最终决定不在此处对这两种模式做任何定义或引用，请读者自行参阅相关文档资料。</font></span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><strong><span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">回调函数和模板方法模式在单元测试中的应用</span></strong><strong><span style="font-size: 12pt; line-height: 150%;" lang="EN-US"> </span></strong><span lang="EN-US">
</span>
</p>
<p>&nbsp;</p>
<font size="3"><span style="font-family: 宋体;">上一节我们简单的回顾了回调函数和模板方法模式，</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">框架中大量采用了这两种设计模式，有兴趣的读者可以阅读</span><span lang="EN-US"><font>Spring</font></span><span style="font-family: 宋体;">框架代码进一步巩固对这两种模式的理解和运用。本节将结合回调函数模式和模板方法模式对前面的测试用例进行重构，读者可以在重构过程中逐步了解这两种设计模式的运用。</span></font>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">首先，让我们简单总结一下前面测试用例的问题：</font></span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><strong><span lang="EN-US"><span><font><font size="3">一、</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span></strong><strong><font size="3"><span style="font-family: 宋体;">抽象层次太低，不够通用？</span><span lang="EN-US"> </span></font></strong></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">例如，对于</font></span><span style="font-size: 10pt; background: none repeat scroll 0% 50% silver; color: black; line-height: 150%;" lang="EN-US">getConfigLocations</span><span style="font-size: 10pt; background: none repeat scroll 0% 50% silver; color: black; line-height: 150%; font-family: 宋体;">（）</span><span style="font-family: 宋体;"><font size="3">方法，我们完全可以放到一个父类中实现，因为对于一个项目而言，其配置文件大多都是统一的，没有必要在没有测试类中都定义该方法。</font></span></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* DAL层测试支持类.
*
*
* 除非特殊情况,所有DAO都要继承此类.
*
* @author tao.youzt
*/
public abstract class GodzillaDalTestSupport extends AbstractDependencyInjectionSpringContextTests {
/*
* @see org.springframework.test.AbstractDependencyInjectionSpringContextTests#getConfigLocations()
*/
@Override
protected final String[] getConfigLocations() {
String[] configLocations = null;
String[] customConfigLocations = getCustomConfigLocations();
if (customConfigLocations != null &amp;&amp; customConfigLocations.length &gt; 0) {
configLocations = new String[customConfigLocations.length + 2];
configLocations[0] = "classpath:godzilla/dal/godzilla-db-test.xml";
configLocations[1] = "classpath:godzilla/dal/godzilla-dao.xml";
for (int i = 2; i &lt; configLocations.length; i++) {
configLocations[i] = customConfigLocations[i - 2];
}
return configLocations;
} else {
return new String[] { "classpath:godzilla/dal/godzilla-db-test.xml",
"classpath:godzilla/dal/godzilla-dao.xml" };
}
}
/**
* 子类可以覆盖该方法加载个性化配置.
*
* @return
*/
protected String[] getCustomConfigLocations() {
return null;
}
}</pre>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">如图所示，我们提炼了一个抽象支持类，实现了</font></span><span style="font-size: 10pt; background: none repeat scroll 0% 50% silver; color: black; line-height: 150%;" lang="EN-US">getConfigLocations</span><span style="font-size: 10pt; background: none repeat scroll 0% 50% silver; color: black; line-height: 150%; font-family: 宋体;">（）</span><span style="font-family: 宋体;"><font size="3">方法，同时还提供了</font></span><span style="font-size: 10pt; background: none repeat scroll 0% 50% #d9d9d9; color: black; line-height: 150%;" lang="EN-US">getCustomConfigLocations()</span><span style="font-family: 宋体;"><font size="3">方法供子类使用，子类可以通过重载该方法提供定制的配置。</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font></p>
</span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">有了该支持类，具体测试类只需要继承该类并编写测试逻辑即可。</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><strong><span lang="EN-US"><span><font><font size="3">二、</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span></strong><strong><font size="3"><span style="font-family: 宋体;">缺少准备测试环境和清除测试数据的环节？</span><span lang="EN-US">
<p>&nbsp;</p>
</span></font></strong></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">对
于大多数测试用例，可能都会涉及到初始化数据和清除测试数据的问题，最典型的就是数据库操作，这也是本文采用数据库操作作为案例的原因。那么如何实现呢？
很显然在每个测试方法中都编写准备环境和清除测试数据的代码是不合适的，因为大多数时候对于一个测试类而言，准备环境和清除数据的逻辑都是一样的。聪明的
你一定会想到定义两个方法，一个初始化环境，一个清除测试数据。是的，就是这样！</font></span></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* @author tao.youzt
*/
public class TestBizUrlDAO extends AbstractDependencyInjectionSpringContextTests {
private BizUrlDAO bizUrlDAO;
@Override
protected String[] getConfigLocations() {
return new String[]{"godzilla-dao.xml","godzilla-db.xml"};
}
protected void setupEnv(){
bizUrlDAO.delete("www.easyjf.com");
}
protected void cleanEnv(){
bizUrlDAO.delete("www.easyjf.com");
}
public void testTemp(){
setupEnv();
bizUrlDAO.insert(generateDO());
assertNotNull(bizUrlDAO.getByUrl("www.easyjf.com"));
setupEnv();
}
}</pre>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><font size="3"><span style="font-family: 宋体;">如你所见，我们在这里定义了</span><span lang="EN-US"><font>setupEnv()</font></span><span style="font-family: 宋体;">和</span><span lang="EN-US"><font>cleanEnv()</font></span><span style="font-family: 宋体;">两个方法，分别用于初始化环境和清除测试数据，然后在测试方法开始和结束时分别调用这两个方法。这的确达到了我们的目的，不用在每个测试方法中都编写初始化和清除逻辑！但此时你一定发现在每个测试方法前后都调用</span><span lang="EN-US"><font>setupEnv()</font></span><span style="font-family: 宋体;">和</span><span lang="EN-US"><font>cleanEnv()</font></span><span style="font-family: 宋体;">也很不爽，那说明我们的抽象程度还不够！那么该如何做的更好呢？</span></font></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span lang="EN-US">
<p><font size="3">&nbsp;</font></p>
</span></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><font size="3"><span style="font-family: 宋体;">这里该到模板方法（</span><span lang="EN-US"><font>Template Method</font></span><span style="font-family: 宋体;">）模式发挥威力的时候了。我们将使用模板方法来继续重构前面的案例。让我们先来定义一个方法：</span></font></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* @author tao.youzt
*/
public class TestBizUrlDAO extends AbstractDependencyInjectionSpringContextTests {
private BizUrlDAO bizUrlDAO;
@Override
protected String[] getConfigLocations() {
return new String[]{"godzilla-dao.xml","godzilla-db.xml"};
}
protected void setupEnv(){
bizUrlDAO.delete("www.easyjf.com");
}
protected void cleanEnv(){
bizUrlDAO.delete("www.easyjf.com");
}
public void testTemp(){
//do test logic in this method
execute();
}
protected void execute(){
setupEnv();
doTestLogic();
setupEnv();
}
}</pre>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><font size="3"><span style="font-family: 宋体;">相比之前的方法，我们这里已经有了一些进步，定义了一个</span><span lang="EN-US"><font>execute</font></span><span style="font-family: 宋体;">方法，在该方法开始和结束分别执行初始化和清除逻辑，然后由</span><span lang="EN-US"><font>doTestLogic()</font></span><span style="font-family: 宋体;">方法实现测试逻辑。实际测试方法中只要执行</span><span lang="EN-US"><font>execute</font></span><span style="font-family: 宋体;">方法，并传入测试逻辑就可以了。瞧，不经意间我们已经实现了模板方法模式——<strong>把通用的逻辑封转起来，变化的部分由具体方法提供</strong>。怎么，不相信么？呵呵，设计模式其实并不复杂，就是前人解决通用问题的一些最佳实践总结而已。</span></font></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><strong><span style="font-size: 9pt; color: green; line-height: 150%; font-family: 宋体;">此时你可能会说，</span></strong><strong><span style="font-size: 9pt; color: green; line-height: 150%;" lang="EN-US"><font>TeseCase</font></span></strong><strong><span style="font-size: 9pt; color: green; line-height: 150%; font-family: 宋体;">类已经提供了</span></strong><strong><span style="font-size: 9pt; color: green; line-height: 150%;" lang="EN-US"><font>setUp()</font></span></strong><strong><span style="font-size: 9pt; color: green; line-height: 150%; font-family: 宋体;">和</span></strong><strong><span style="font-size: 9pt; color: green; line-height: 150%;" lang="EN-US"><font>tearDown()</font></span></strong><strong><span style="font-size: 9pt; color: green; line-height: 150%; font-family: 宋体;">方法来做这件事情，我也想到了，哈哈！但这并不和本文产生冲突！</span><span style="font-size: 9pt; color: green; line-height: 150%;" lang="EN-US">
<p><span lang="EN-US">
</span>
</p>
<p>&nbsp;</p>
<font size="3"><span style="font-family: 宋体;">问题似乎越来越清晰，但我们遭遇了一条无法跨越的鸿沟——<strong>如何才能把测试逻辑传递到</strong></span><strong><span lang="EN-US"><font>execute</font></span></strong><strong><span style="font-family: 宋体;">方法中呢</span></strong><span style="font-family: 宋体;">？单靠传统的编程方法已经无法解决这个问题，因此我们必须寻找其他途径。</span></font><span lang="EN-US"><font size="3">&nbsp;</font></span></span></strong></p>
</span><strong></strong>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">可能此时此刻你已经想到，本文另一个重要概念——回调方法模式还没有用到，是不是该使用该模式了？没错，就是它了！我先把代码给出，然后再详细解释。</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><font size="3"><span style="font-family: 宋体;">我们提供了一个抽象类</span><span lang="EN-US"><font>TestExecutor</font></span><span style="font-family: 宋体;">，并定义一个抽象的</span><span lang="EN-US"><font>execute</font></span><span style="font-family: 宋体;">方法，然后为测试类的</span><span lang="EN-US"><font>execute</font></span><span style="font-family: 宋体;">方法传入一个</span><span lang="EN-US"><font>TestExecutor</font></span><span style="font-family: 宋体;">的实例，并调用该实例的</span><span lang="EN-US"><font>execute</font></span><span style="font-family: 宋体;">方法。最后，我们的测试方法中只需要</span><span lang="EN-US"><font>new</font></span><span style="font-family: 宋体;">一个</span><span lang="EN-US"><font>TestExecutor</font></span><span style="font-family: 宋体;">，并在</span><span lang="EN-US"><font>execute</font></span><span style="font-family: 宋体;">方法中实现测试逻辑，便可以按照预期的方式执行：准备测试环境</span><span lang="EN-US"><font>-</font></span><span style="font-family: 宋体;">执行测试逻辑</span><span lang="EN-US"><font>-</font></span><span style="font-family: 宋体;">清除测试数据。这便是一个典型的回调方法模式的应用！</span></font></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">模板方法和回调函数模式说起来挺悬，其实也就这么简单，明白了吧：）</font></span></p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: -21pt; line-height: 150%;"><strong><span lang="EN-US"><span><font><font size="3">三、</font><span style="font-weight: normal; font-size: 7pt; line-height: normal; font-style: normal; font-variant: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span></strong><strong><font size="3"><span style="font-family: 宋体;">如何为每个测试方法单独提供环境方法呢？</span><span lang="EN-US">&nbsp;&nbsp;</span></font></strong></p>
<p>&nbsp;</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span style="font-family: 宋体;"><font size="3">通过前面的讲解，相信大家对模板方法和回调函数模式都已经掌握了，这里直接给出相关代码：</font></span></p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* DAL层测试支持类.
*
*
* 除非特殊情况,所有DAO都要继承此类.
*
* @author tao.youzt
*/
public abstract class GodzillaDalTestSupport extends AbstractDependencyInjectionSpringContextTests {
/*
* @see org.springframework.test.AbstractDependencyInjectionSpringContextTests#getConfigLocations()
*/
@Override
protected final String[] getConfigLocations() {
String[] configLocations = null;
String[] customConfigLocations = getCustomConfigLocations();
if (customConfigLocations != null &amp;&amp; customConfigLocations.length &gt; 0) {
configLocations = new String[customConfigLocations.length + 2];
configLocations[0] = "classpath:godzilla/dal/godzilla-db-test.xml";
configLocations[1] = "classpath:godzilla/dal/godzilla-dao.xml";
for (int i = 2; i &lt; configLocations.length; i++) {
configLocations[i] = customConfigLocations[i - 2];
}
return configLocations;
} else {
return new String[] { "classpath:godzilla/dal/godzilla-db-test.xml",
"classpath:godzilla/dal/godzilla-dao.xml" };
}
}
/**
* 子类可以覆盖该方法加载个性化配置.
*
* @return
*/
protected String[] getCustomConfigLocations() {
return null;
}
/**
* 准备测试环境.
*/
protected void setupEnv() {
}
/**
* 清除测试数据.
*/
protected void cleanEvn() {
}
/**
* 测试用例执行器.
*/
protected abstract class TestExecutor {
/**
* 准备测试环境
*/
public void setupEnv() {
}
/**
* 执行测试用例.
*/
public abstract void execute();
/**
* 清除测试数据.
*/
public void cleanEnv() {
}
}
/**
* 执行一个测试用例.
*
* @param executor
*/
protected final void execute(final TestExecutor executor) {
execute(IgnoralType.NONE, executor);
}
/**
* 执行一个测试用例.
*
* @param executor
*/
protected final void execute(final IgnoralType ignoral, final TestExecutor executor) {
switch (ignoral) {
case NONE: {
setupEnv();
executor.setupEnv();
executor.execute();
executor.cleanEnv();
cleanEvn();
break;
}
case BOTH: {
executor.execute();
break;
}
case GLOBAL: {
executor.setupEnv();
executor.execute();
executor.cleanEnv();
break;
}
case LOCAL: {
setupEnv();
executor.execute();
cleanEvn();
break;
}
case GLOBAL_S: {
executor.setupEnv();
executor.execute();
executor.cleanEnv();
cleanEvn();
break;
}
case GLOBAL_C: {
setupEnv();
executor.setupEnv();
executor.execute();
executor.cleanEnv();
break;
}
case LOCAL_S: {
setupEnv();
executor.execute();
executor.cleanEnv();
cleanEvn();
break;
}
case LOCAL_C: {
setupEnv();
executor.setupEnv();
executor.execute();
cleanEvn();
break;
}
case BOTH_SETUP: {
executor.execute();
executor.cleanEnv();
cleanEvn();
break;
}
case BOTH_CLEAN: {
setupEnv();
executor.setupEnv();
executor.execute();
break;
}
case GLOBAL_S_LOCAL_C: {
executor.setupEnv();
executor.execute();
cleanEvn();
break;
}
case GLOBAL_C_LOCAL_S: {
setupEnv();
executor.execute();
executor.cleanEnv();
break;
}
}
}
/**
* 忽略类型Enum.
*/
public enum IgnoralType {
/** 不忽略任何环境相关方法 */
NONE,
/** 忽略全局环境相关方法 */
GLOBAL,
/** 忽略局部环境相关方法 */
LOCAL,
/** 忽略所有环境相关方法 */
BOTH,
/** 忽略全局准备测试环境方法 */
GLOBAL_S,
/** 忽略全局清除测试数据方法 */
GLOBAL_C,
/** 忽略局部准备测试环境方法 */
LOCAL_S,
/** 忽略局部清除测试数据方法 */
LOCAL_C,
/** 忽略全部准备测试环境方法 */
BOTH_SETUP,
/** 忽略全部清楚测试数据方法 */
BOTH_CLEAN,
/** 忽略全局准备测试环境和局部清除测试数据方法 */
GLOBAL_S_LOCAL_C,
/** 忽略全局清除测试数据和局部准备测试环境方法 */
GLOBAL_C_LOCAL_S
}
}</pre>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 42pt; text-indent: 21pt; line-height: 150%;"><span lang="EN-US">
</span></p>
<p>&nbsp;</p>
<pre style="border: 1px dotted #999999; padding: 5px; font-size: 12px; margin-left: 10px; margin-right: 10px; background-color: #eeeeee;">/**
* URL DAO测试类.
*
* @author tao.youzt
*/
public class TestBizUrlDAO extends GodzillaDalTestSupport {
private BizUrlDAO bizUrlDAO;
@Override
protected void setupEnv() {
bizUrlDAO.delete("www.easyjf.com");
}
@Override
protected void cleanEvn() {
bizUrlDAO.delete("www.easyjf.com");
}
/**
* 测试插入一条新数据.
*/
public void testInsert() {
execute(new TestExecutor() {
@Override
public void execute() {
bizUrlDAO.insert(generateDO());
assertNotNull(bizUrlDAO.getByUrl("www.easyjf.com"));
}
});
}
/**
* 测试重复插入数据的情况.
*/
public void testDuplicateInsert() {
execute(new TestExecutor() {
@Override
public void setupEnv() {
bizUrlDAO.insert(generateDO());
}
@Override
public void execute() {
try {
bizUrlDAO.insert(generateDO());
assertTrue("Must throw an exception!", false);
} catch (Exception e) {
assertTrue("Expect this exception.", true);
}
}
});
}
/**
* 测试删除一条已经存在的数据.
*/
public void testDelete() {
execute(IgnoralType.GLOBAL_C, new TestExecutor() {
@Override
public void execute() {
assertNotNull(bizUrlDAO.getByUrl("www.easyjf.com"));
bizUrlDAO.delete("www.easyjf.com");
assertNull(bizUrlDAO.getByUrl("www.easyjf.com"));
}
@Override
public void setupEnv() {
bizUrlDAO.insert(generateDO());
}
});
}
/**
* 生成一个用于测试的DO.
*
* @return
*/
private BizUrlSynchronizeDO generateDO() {
BizUrlDO bizUrlDO = new BizUrlDO();
bizUrlDO.setUrl("www.easyjf.com");
bizUrlDO.setName("EasyJWeb");
bizUrlDO.setEmail("webmaster@easyjf.com");
return bizUrlDO;
}
public void setBizUrlDAO(BizUrlSynchronzieDAO bizUrlDAO) {
this.bizUrlDAO = bizUrlDAO;
}
}</pre>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; line-height: 150%;"><font size="3"><span lang="EN-US"><span><font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span style="font-family: 宋体;">注意</span><span lang="EN-US"><font>testDeleate()</font></span><span style="font-family: 宋体;">方法，我们传入了两个参数，第一个参数</span></font><span>IgnoralType.GLOBAL_C</span><font size="3"><span lang="EN-US"><font> </font></span><span style="font-family: 宋体;">代表忽略哪个方法，有</span><span lang="EN-US"><font>12</font></span><span style="font-family: 宋体;">种类型可以设置。</span><span lang="EN-US"><font>GLOBAL_C</font></span><span style="font-family: 宋体;">代表忽略全局的清除测试数据方法，其他见代码注释。</span></font></p>
</font><br />
<br />
<br />
<br />
<br />
<img src ="http://www.blogjava.net/ashutc/aggbug/333349.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ashutc/" target="_blank">西瓜</a> 2010-09-29 13:25 <a href="http://www.blogjava.net/ashutc/archive/2010/09/29/333349.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>