<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>wing929</title>
    <description></description>
    <link>http://wing929.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>gienah-testing5分钟教程</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/230721" style="color:red;">http://wing929.javaeye.com/blog/230721</a>&nbsp;
          发表时间: 2008年08月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <span style="font-size: large">SpringRunner</span><br /><br />    gienah-testing项目主要包含2个Junit4的运行类: springrunner和springtransactionalrunner 。springrunner负责访问主要项目特性的引擎：bean注入。Springtransactionalrunner继承于springrunner ，允许有标记的事务测试在测试完成时将回滚到测试发生前。 <br />     使用这些类，你需要在您的测试类中标记JUnit 4的@ runwith。此外，您必须使用gienah-testing的@Configuration标记来指定Spring的配置文件。看看下面的例子：<br /><br /><pre name="code" class="java">@RunWith(value = SpringRunner.class)
@Configuration(locations = {"resources/spring/spring-beans.xml"})
public class TestUserService {

    @Test public void someTestMethod() { 
        ...
    }

    ...
}</pre><br /><br />    我们现在为业务类写一个测试类,请留意@Configuration标记,你可以使用locations属性来指定spring配置文件的路径,将会获取到spring的上下文.还可以以其它方式来使用@Configuration标记, 但这些将经过审查后再补上。<br />	现在,将通过配置运行类来获取变量,请留意下面的例子:<br /><pre name="code" class="java">@RunWith(value = SpringRunner.class)
@Configuration(locations = {"resources/spring/spring-beans.xml"})
public class TestUserService {
 
    @Dependency
    private SomeBean someBean;

    @Test 
    public void someTestMethod() { 
       Assert.assertNotNull(this.someBean);
    }

}</pre><br />	使用@Dependency标记可以使我们有效地使配置文件中bean的”somebean”注入到我们的测试类中,因此字段将会以实例的方式出现在我们的代码中,那么,运行类将查找spring上下文中相对应的指定的bean名称的bean,如果你想装载一个bean使用不同的名称,你可以利用@Dependency标记来为它指定”bean”属性.<br />    另外介绍的特性就是允许使用测试标记在测试完成后使事务自动回滚到测试前。例如，下面的示例：<br /><pre name="code" class="java">@RunWith(value = SpringRunner.class)
@Configuration(locations = {"resources/spring/spring-beans.xml"})
public class TestUserService {
    @Test 
    @Transactional  
    public void someTestMethod() { 
       // doing some database stuff here...
    }
    @Test 
    public void someOtherTestMethod() { 
       // doing some database stuff here...
    }
}</pre><br />    "someTestMethod"方法以事务的方式执行，而"someOtherTestMethod"方法则不会。当"someTestMethod"方法执行完后，事务将使数据库状态回滚到未执行前，你也可以使全部测试类都标记@Transactional属性，所有的测试方法都将在事务中执行<br /><br /><span style="font-size: large">配置spring上下文</span><br />	在前面的话题中，你在执行中见到@Configuration标记。这个标记有三个属性：locations, provider和loader，下面让我们看看如何去使用它们:<br />   •locations(可选择):使用这个属性你可以使用指定的spring配置文件来生成spring上下文<br />    •provider(可选择):使用这个属性允许你指定一个类同样可以使用@Configuration标记去指定locations的属性,这里允许一个特许的locations说明在一个测试类套件中<br />    •loader(可选择):这个属性允许你去指定IcontextLoader的实现并去使用它构成spring上下文.一般情况使用默认配置已经足够.<br /><br />    @Configuration标记至少指定locations或者provider其中一个属性,如果没有它们其中的一个,将会使JUnit 4显示出一个error.让我们去看看提供的属性在执行中的实例:<br /><pre name="code" class="java">@RunWith(value = SpringRunner.class)
@Configuration(locations = {"resources/spring/spring-beans.xml"})
public class Test1 {
   ...
  
} 
@RunWith(value = SpringRunner.class)
@Configuration(provider = Test1.class)
public class Test2 {
   ...
}
@RunWith(value = SpringRunner.class)
@Configuration(provider = Test2.class)
public class Test3 {
   ... 
}</pre><br />    注意怎样去构造est3从Test2到Test1经过的流程.这个方式(通过路径获取获取配置)是不平常和不受欢迎的,但我想说明一下这个表现.<br /><br /><span style="font-size: large">访问spring上下文</span><br />	最后,也可以直接获取spring上下文,如果这样,你应该在你的测试中创建一个局部变量和写上一个@Context标记:<br /><pre name="code" class="java">@RunWith(value = SpringRunner.class)
@Configuration(locations = {"resources/spring/spring-beans.xml"})
public class TestUserService {
    @Context
    private ConfigurableApplicationContext context; 
    @Test 
    public void someTestMethod() { 
       SomeBean someBean = context.getBean("someBean");
       ... 
    }
}</pre><br />	当这样做时,运行类将可以使spring上下文将会自动地注入到你的测试类中,然后你也可以访问它.<br /><br /><span style="font-size: large">配置文件解释</span><br />	EO.2版本改变了单元测试类的配置,以springXML配置文件的方式填写.因此, 该项目引进一个新的标签:gienah.<br />	在gienah架构下使用标签,你必须在你的springXML配置文件的头部导入声明,<br />The emboldened text in the XML sample below represents the additions you must add to get access to the tags contained in the gienah namespaces.<br /><pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;beans 
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:gienah="http://www.springframework.org/schema/gienah"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans                 
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/gienah
       http://www.springframework.org/schema/gienah/gienah.xsd">
    ... 
&lt;/beans>

&lt;gienah:test /></pre>标签<br />	这个标签在springXML配置文件中定义一个测试类,看下面的例子去了解通常的情况:<br /><pre name="code" class="xml">&lt;gienah:test class="org.myproject.test.TestSample1" transactional="true">
    &lt;gienah:dependency property="someBean1" bean="someBean" />
    &lt;gienah:context property="applicationContext" />
    &lt;gienah:method name="testSomething" transactional="true" ignore="false" />
&lt;/gienah:test></pre><br />This approach is equals to mark the someBean1 class attribute with the @Dependency annotation. Latest, using the &lt;gienah:context /> tag you can inject the application context to the Test class. <br /><br />   上述实例在配置文件中把一个测试类命名为TestSample1,它指定了在"testSomething"方法中将支持事务,而其它方法即不运行在同一个事务中(查看主事务属性和&lt;gienah:method />标签),另一个有趣的事情是使用&lt;gienah:dependency />可以定义要注入的bean,这样基本上等同于使用@Dependency标记来标注someBean1类属性,最后,使用&lt;gienah:context />标签你可以在测试类中注入应用上下文
          <br/>
          <span style="color:red;">
            <a href="http://wing929.javaeye.com/blog/230721#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 20 Aug 2008 15:31:50 +0800</pubDate>
        <link>http://wing929.javaeye.com/blog/230721</link>
        <guid>http://wing929.javaeye.com/blog/230721</guid>
      </item>
      <item>
        <title>创建EXCEL文件</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/230560" style="color:red;">http://wing929.javaeye.com/blog/230560</a>&nbsp;
          发表时间: 2008年08月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          创建EXCEL文件:<br /><br /><pre name="code" class="java">public void createXLS()
 {
  try
  {
   //打开文件
   WritableWorkbook book=
   Workbook.createWorkbook(new File("JAVA生成EXCEL测试.xls"));
   
   //生成名为"第一页"的工作表，参数0表示这是第一页
   WritableSheet sheet=book.createSheet("第一页",0);
   

  //设置字体为宋体,16号字,加粗,颜色为红色
   WritableFont font1=new WritableFont(WritableFont.createFont("宋体"),16,WritableFont.BOLD); 
   font1.setColour(Colour.RED);
   WritableCellFormat format1=new WritableCellFormat(font1);
   format1.setAlignment(jxl.format.Alignment.CENTRE);
   format1.setVerticalAlignment(jxl.format.VerticalAlignment.CENTRE);
   
   Label labelA = new Label(0,0,"姓名",format1);
   Label labelB = new Label(1,0,"地址",format1);
   Label labelC = new Label(2,0,"年龄",format1);
 
   //将定义好的单元格添加到工作表中
   sheet.addCell(labelA);
   sheet.addCell(labelB);
   sheet.addCell(labelC);
   
   /*生成一个保存数字的单元格
   必须使用Number的完整包路径，否则有语法歧义*/
   Label labelA1 = new Label(0,1,"小张");
   Label labelB1 = new Label(1,1,"湖南省长沙市");
   jxl.write.Number number = new jxl.write.Number(2,1,20);
   sheet.addCell(labelA1);
   sheet.addCell(labelB1);
   sheet.addCell(number);
   
   //写入数据并关闭文件
   book.write();
   book.close();
   System.out.println("创建文件成功!");
  
  }catch(Exception e)
  {
   System.out.println(e);
  }
 }

读EXCEL文件:

public void readXLS()
 {
  try
   {
   Workbook book=
   Workbook.getWorkbook(new File("JAVA生成EXCEL测试.xls"));
   
   //获得第一个工作表对象
   Sheet sheet=book.getSheet(0);
   
   //得到第一列第一.二行的单元格
   Cell cellKey=sheet.getCell(0,0);   
   Cell cellValue=sheet.getCell(0,1);
   String result=cellKey.getContents();
   String value=cellValue.getContents();   
   System.out.println(result+" :"+value);
   
   cellKey=sheet.getCell(1,0);
   cellValue=sheet.getCell(1,1);
   result=cellKey.getContents();
   value=cellValue.getContents(); 
   System.out.println(result+" :"+value);
   
   cellKey=sheet.getCell(2,0);
   cellValue=sheet.getCell(2,1);
   result=cellKey.getContents();
   value=cellValue.getContents();
   System.out.println(result+" :"+value);
   
   book.close();
   System.out.println("查询EXCEL文件结束!");
   }catch(Exception e)
   {
    System.out.println(e);
   }

 }

编辑EXCEL文件和创建差不多,只是在创建文件时有一点不同:

//Excel获得文件
   Workbook wb=Workbook.getWorkbook(new File("JAVA生成EXCEL测试.xls"));
   
   //打开一个文件的副本，并且指定数据写回到原文件
   WritableWorkbook book=
   Workbook.createWorkbook(new File("JAVA生成EXCEL测试.xls"),wb); </pre>
          <br/>
          <span style="color:red;">
            <a href="http://wing929.javaeye.com/blog/230560#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 20 Aug 2008 11:30:46 +0800</pubDate>
        <link>http://wing929.javaeye.com/blog/230560</link>
        <guid>http://wing929.javaeye.com/blog/230560</guid>
      </item>
      <item>
        <title>JAVA EXCEL API 使用</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/230513" style="color:red;">http://wing929.javaeye.com/blog/230513</a>&nbsp;
          发表时间: 2008年08月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          JAVA EXCEL API简介 <br /> <br />Java Excel是一开放源码项目，通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。使用该API非Windows操作系统也可以通过纯Java应用来处理Excel数据表。因为是使用Java编写的，所以我们在Web应用中可以通过JSP、Servlet来调用API实现对Excel数据表的访问。 <br /> <br />现在发布的稳定版本是V2.0，提供以下功能： <br /> <br />从Excel 95、97、2000等格式的文件中读取数据； <br />读取Excel公式（可以读取Excel 97以后的公式）； <br />生成Excel数据表（格式为Excel 97）； <br />支持字体、数字、日期的格式化； <br />支持单元格的阴影操作，以及颜色操作； <br />修改已经存在的数据表； <br />现在还不支持以下功能，但不久就会提供了： <br /> <br />不能够读取图表信息； <br />可以读，但是不能生成公式，任何类型公式最后的计算值都可以读出； <br />应用示例 <br /> <br />1 从Excel文件读取数据表 <br /> <br />Java Excel API既可以从本地文件系统的一个文件(.xls)，也可以从输入流中读取Excel数据表。读取Excel数据表的第一步是创建Workbook(术语：工作薄)，下面的代码片段举例说明了应该如何操作：(完整代码见ExcelReading.java) <br /> <br /><br />import java.io.*;<br />import jxl.*;<br />… … … …<br />try<br />{<br />//构建Workbook对象, 只读Workbook对象<br />//直接从本地文件创建Workbook<br />//从输入流创建Workbook<br />InputStream is = new FileInputStream(sourcefile);<br />jxl.Workbook rwb = Workbook.getWorkbook(is);<br />}<br />catch (Exception e)<br />{<br />e.printStackTrace();<br />} <br /> <br /> <br /> <br />一旦创建了Workbook，我们就可以通过它来访问Excel Sheet(术语：工作表)。参考下面的代码片段： <br /> <br /><br />//获取第一张Sheet表<br />Sheet rs = rwb.getSheet(0); <br /> <br /> <br /> <br />我们既可能通过Sheet的名称来访问它，也可以通过下标来访问它。如果通过下标来访问的话，要注意的一点是下标从0开始，就像数组一样。 <br /> <br />一旦得到了Sheet，我们就可以通过它来访问Excel Cell(术语：单元格)。参考下面的代码片段： <br /> <br /><br />//获取第一行，第一列的值<br />Cell c00 = rs.getCell(0, 0);<br />String strc00 = c00.getContents(); <br /> <br />//获取第一行，第二列的值<br />Cell c10 = rs.getCell(1, 0);<br />String strc10 = c10.getContents(); <br /> <br />//获取第二行，第二列的值<br />Cell c11 = rs.getCell(1, 1);<br />String strc11 = c11.getContents(); <br /> <br />System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());<br />System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());<br />System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType()); <br /> <br /> <br /> <br />如果仅仅是取得Cell的值，我们可以方便地通过getContents()方法，它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型，Cell(1, 0)是数字型，Cell(1,1)是日期型，通过getContents()，三种类型的返回值都是字符型。 <br /> <br />如果有需要知道Cell内容的确切类型，API也提供了一系列的方法。参考下面的代码片段： <br /> <br /><br />String strc00 = null;<br />double strc10 = 0.00;<br />Date strc11 = null; <br /> <br />Cell c00 = rs.getCell(0, 0);<br />Cell c10 = rs.getCell(1, 0);<br />Cell c11 = rs.getCell(1, 1); <br /> <br />if(c00.getType() == CellType.LABEL)<br />{<br />LabelCell labelc00 = (LabelCell)c00;<br />strc00 = labelc00.getString();<br />}<br />if(c10.getType() == CellType.NUMBER)<br />{<br />NmberCell numc10 = (NumberCell)c10;<br />strc10 = numc10.getValue();<br />}<br />if(c11.getType() == CellType.DATE)<br />{<br />DateCell datec11 = (DateCell)c11;<br />strc11 = datec11.getDate();<br />} <br /> <br />System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());<br />System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());<br />System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType()); <br /> <br /> <br /> <br />在得到Cell对象后，通过getType()方法可以获得该单元格的类型，然后与API提供的基本类型相匹配，强制转换成相应的类型，最后调用相应的取值方法getXXX()，就可以得到确定类型的值。API提供了以下基本类型，与Excel的数据格式相对应，如下图所示： <br /> <br /> <br /> <br /> <br /> <br />每种类型的具体意义，请参见Java Excel API Document。 <br /> <br />当你完成对Excel电子表格数据的处理后，一定要使用close()方法来关闭先前创建的对象，以释放读取数据表的过程中所占用的内存空间，在读取大量数据时显得尤为重要。参考如下代码片段： <br /> <br /><br />//操作完成时，关闭对象，释放占用的内存空间<br />rwb.close(); <br /> <br /> <br /> <br />Java Excel API提供了许多访问Excel数据表的方法，在这里我只简要地介绍几个常用的方法，其它的方法请参考附录中的Java Excel API Document。 <br /> <br />Workbook类提供的方法 <br /> <br />1. int getNumberOfSheets()<br />获得工作薄（Workbook）中工作表（Sheet）的个数，示例： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />int sheets = rwb.getNumberOfSheets(); <br /> <br /> <br /> <br />2. Sheet[] getSheets()<br />返回工作薄（Workbook）中工作表（Sheet）对象数组，示例： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />Sheet[] sheets = rwb.getSheets(); <br /> <br /> <br /> <br />3. String getVersion()<br />返回正在使用的API的版本号，好像是没什么太大的作用。 <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />String apiVersion = rwb.getVersion(); <br /> <br /> <br /> <br />Sheet接口提供的方法 <br /> <br />1) String getName()<br />获取Sheet的名称，示例： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />jxl.Sheet rs = rwb.getSheet(0);<br />String sheetName = rs.getName(); <br /> <br /> <br /> <br />2) int getColumns()<br />获取Sheet表中所包含的总列数，示例： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />jxl.Sheet rs = rwb.getSheet(0);<br />int rsColumns = rs.getColumns(); <br /> <br /> <br /> <br />3) Cell[] getColumn(int column)<br />获取某一列的所有单元格，返回的是单元格对象数组，示例： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />jxl.Sheet rs = rwb.getSheet(0);<br />Cell[] cell = rs.getColumn(0); <br /> <br /> <br /> <br />4) int getRows()<br />获取Sheet表中所包含的总行数，示例： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />jxl.Sheet rs = rwb.getSheet(0);<br />int rsRows = rs.getRows(); <br /> <br /> <br /> <br />5) Cell[] getRow(int row)<br />获取某一行的所有单元格，返回的是单元格对象数组，示例子： <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />jxl.Sheet rs = rwb.getSheet(0);<br />Cell[] cell = rs.getRow(0); <br /> <br /> <br /> <br />6) Cell getCell(int column, int row)<br />获取指定单元格的对象引用，需要注意的是它的两个参数，第一个是列数，第二个是行数，这与通常的行、列组合有些不同。 <br /> <br /><br />jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));<br />jxl.Sheet rs = rwb.getSheet(0);<br />Cell cell = rs.getCell(0, 0); <br /> <br /> <br /> <br />2 生成新的Excel工作薄 <br /> <br />下面的代码主要是向大家介绍如何生成简单的Excel工作表，在这里单元格的内容是不带任何修饰的(如：字体，颜色等等)，所有的内容都作为字符串写入。(完整代码见ExcelWriting.java) <br /> <br />与读取Excel工作表相似，首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象，这里要注意的是，只能通过API提供的工厂方法来创建Workbook，而不能使用WritableWorkbook的构造函数，因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下： <br /> <br /><br /><pre name="code" class="java">import java.io.*;
import jxl.*;
import jxl.write.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//Method 1：创建可写入的Excel工作薄
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile)); 
 
//Method 2：将WritableWorkbook直接写入到输出流
/*
OutputStream os = new FileOutputStream(targetfile);
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
*/
}
catch (Exception e)
{
e.printStackTrace();
} </pre><br /> <br /> <br /> <br />API提供了两种方式来处理可写入的输出流，一种是直接生成本地文件，如果文件名不带全路径的话，缺省的文件会定位在当前目录，如果文件名带有全路径的话，则生成的Excel文件则会定位在相应的目录；另外一种是将Excel对象直接写入到输出流，例如：用户通过浏览器来访问Web服务器，如果HTTP头设置正确的话，浏览器自动调用客户端的Excel应用程序，来显示动态生成的Excel电子表格。 <br /> <br />接下来就是要创建工作表，创建工作表的方法与创建工作薄的方法几乎一样，同样是通过工厂模式方法获得相应的对象，该方法需要两个参数，一个是工作表的名称，另一个是工作表在工作薄中的位置，参考下面的代码片段： <br /> <br /><br />//创建Excel工作表<br />jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0); <br /> <br /> <br /> <br />"这锅也支好了，材料也准备齐全了，可以开始下锅了！"，现在要做的只是实例化API所提供的Excel基本数据类型，并将它们添加到工作表中就可以了，参考下面的代码片段： <br /> <br /><br />//1.添加Label对象<br /><pre name="code" class="java">jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
ws.addCell(labelC); 
 
//添加带有字型Formatting的对象
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
ws.addCell(labelCF); 
 
//添加带有字体颜色Formatting的对象
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
Underlinestyle.NO_UNDERLINE, jxl.format.Colour.RED);
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC);
ws.addCell(labelCF); </pre><br /> <br />//2.添加Number对象<br /><pre name="code" class="java">jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
ws.addCell(labelN); 
 
//添加带有formatting的Number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF); 
 
//3.添加Boolean对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB); 
 
//4.添加DateTime对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
ws.addCell(labelDT); 
 
//添加带有formatting的DateFormat对象
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
ws.addCell(labelDTF);</pre> <br /> <br /> <br /> <br />这里有两点大家要引起大家的注意。第一点，在构造单元格时，单元格在工作表中的位置就已经确定了。一旦创建后，单元格的位置是不能够变更的，尽管单元格的内容是可以改变的。第二点，单元格的定位是按照下面这样的规律(column, row)，而且下标都是从0开始，例如，A1被存储在(0, 0)，B1被存储在(1, 0)。 <br /> <br />最后，不要忘记关闭打开的Excel工作薄对象，以释放占用的内存，参见下面的代码片段： <br /> <br /><br />//写入Exel工作表<br />wwb.write(); <br /> <br />//关闭Excel工作薄对象<br />wwb.close(); <br /> <br /> <br /> <br />这可能与读取Excel文件的操作有少少不同，在关闭Excel对象之前，你必须要先调用write()方法，因为先前的操作都是存储在缓存中的，所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象，那么只能得到一张空的工作薄了。 <br /> <br />3 拷贝、更新Excel工作薄 <br /> <br />接下来简要介绍一下如何更新一个已经存在的工作薄，主要是下面二步操作，第一步是构造只读的Excel工作薄，第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄，参考下面的代码片段：(完整代码见ExcelModifying.java) <br /> <br /><br /><pre name="code" class="java">//创建只读的Excel工作薄的对象
jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile)); 
 
//创建可写入的Excel工作薄对象
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile), rw); 
 
//读取第一张工作表
jxl.write.WritableSheet ws = wwb.getSheet(0); 
 
//获得第一个单元格对象
jxl.write.WritableCell wc = ws.getWritableCell(0, 0); 
 
//判断单元格的类型, 做出相应的转化
if(wc.getType() == CellType.LABEL)
{
Label l = (Label)wc;
l.setString("The value has been modified.");
} 
 
//写入Excel对象
wwb.write(); 
 
//关闭可写入的Excel对象
wwb.close(); 
 
//关闭只读的Excel对象
rw.close(); </pre> <br /> <br /> <br />之所以使用这种方式构建Excel对象，完全是因为效率的原因，因为上面的示例才是API的主要应用。为了提高性能，在读取工作表时，与数据相关的一些输出信息，所有的格式信息，如：字体、颜色等等，是不被处理的，因为我们的目的是获得行数据的值，既使没有了修饰，也不会对行数据的值产生什么影响。唯一的不利之处就是，在内存中会同时保存两个同样的工作表，这样当工作表体积比较大时，会占用相当大的内存，但现在好像内存的大小并不是什么关键因素了。 <br /> <br />一旦获得了可写入的工作表对象，我们就可以对单元格对象进行更新的操作了，在这里我们不必调用API提供的add()方法，因为单元格已经于工作表当中，所以我们只需要调用相应的setXXX()方法，就可以完成更新的操作了。 <br /> <br />尽单元格原有的格式化修饰是不能去掉的，我们还是可以将新的单元格修饰加上去，以使单元格的内容以不同的形式表现。 <br /> <br />新生成的工作表对象是可写入的，我们除了更新原有的单元格外，还可以添加新的单元格到工作表中，这与示例2的操作是完全一样的。 <br /> <br />最后，不要忘记调用write()方法，将更新的内容写入到文件中，然后关闭工作薄对象，这里有两个工作薄对象要关闭，一个是只读的，另外一个是可写入的。<br /><br /><br /><br /><br /><br />==================================================================== <br />附很久以前写的一个例子： <br /><br /><br /><br /><br /><br /><pre name="code" class="java">package com.mis.Mine; 
  

import java.io.*; 

import java.text.SimpleDateFormat; 

import java.util.ArrayList; 

import java.util.Date; 

import java.util.HashMap; 

import jxl.*; 

import jxl.write.*; 

  

import com.mis.Mine.*; 

  

public class PutOut { 

    public static void main(String args[]) { 

        try { 

  

        /* 制定生成文件地址 */ 

        String filePath = "D:\\Test 报表 .xls" ; 

        WritableWorkbook book; 

        OutputStream os = new FileOutputStream(filePath); 

        book=Workbook.createWorkbook(os); 

        

        //WritableWorkbook book = Workbook.createWorkbook(new File("Test 报表 .xls"));   // 当前目录下 

                                   

            // 生成名为 " 商品信息 " 的工作表，参数 0 表示这是第一页 

            WritableSheet sheet_1 = book.createSheet( "Main" , 0); 

            WritableSheet sheet_2 = book.createSheet( "second" , 1); 

            

        /* 单元格字体格式 */ 

        WritableFont BigTitleFont = new WritableFont(WritableFont.createFont( " 黑体 " ),16, 

                   WritableFont. BOLD , true ,jxl.format.UnderlineStyle. NO_UNDERLINE ,jxl.format.Colour. BROWN ); 

        // 依次为 ： 字体 , 字号 , 加粗 ,boolean, 下划线 , 字体颜色 

        WritableFont NormalTitleFont = new WritableFont(WritableFont. ARIAL ,12,WritableFont. BOLD ); 

        WritableFont BodyFont = new WritableFont(WritableFont. ARIAL ,10); 

        

                                         

        /* 单元格格式 */ 

        WritableCellFormat BigTitle = new WritableCellFormat();   

        BigTitle.setAlignment(jxl.format.Alignment. CENTRE );                    // 居中 

        BigTitle.setBorder(Border. ALL ,BorderLineStyle. THIN );                  // 边框 

        BigTitle.setBackground(jxl.format.Colour. VERY_LIGHT_YELLOW );                  // 颜色 

        BigTitle.setFont(BigTitleFont);                                        // 字体 

        

        WritableCellFormat NormalTitle = new WritableCellFormat();   

        NormalTitle.setAlignment(jxl.format.Alignment. CENTRE );                 // 居中 

        NormalTitle.setBorder(Border. ALL ,BorderLineStyle. THIN );                       // 边框 

        NormalTitle.setBackground(jxl.format.Colour. VERY_LIGHT_YELLOW );           // 颜色 

        NormalTitle.setFont(NormalTitleFont);                                 // 字体 

        

        WritableCellFormat Body = new WritableCellFormat(); 

        Body.setAlignment(jxl.format.Alignment. CENTRE );                        // 居中 

        Body.setBorder(Border. ALL ,BorderLineStyle. THIN );                      // 边框      

        Body.setFont(BodyFont);                                                // 字体 

                      

  

        // 定义当前时间 

            SimpleDateFormat DT = new SimpleDateFormat( "yyyy-MM-dd" );   

            String newdate = DT.format( new Date());   

        sheet_1.addCell( new jxl.write.Label(4,0,newdate)); 

        sheet_1.setColumnView(4,11);                                           // 列宽      

        

        

                                            

            /* 基本单元格设置 */ 

        sheet_1.mergeCells(0,0,3,0);                                          // 合并标题行 

        sheet_1.addCell( new jxl.write.Label(0,0, " 报表 TEST" ,BigTitle));           // 标题行值    

        

        sheet_1.addCell( new jxl.write.Label(0, 1, "polno" ,NormalTitle));           // 第 1 列列名 

        sheet_1.setColumnView(0,16);                                           // 第 1 列列宽 

        sheet_1.addCell( new jxl.write.Label(1, 1, "contno" ,NormalTitle));              // 第 2 列列名 

        sheet_1.setColumnView(1,14);                                           // 第 2 列列宽 

        sheet_1.addCell( new jxl.write.Label(2, 1, "riskcode" ,NormalTitle));         // 第 3 列列名 

        sheet_1.setColumnView(2,10);                                           // 第 3 列列宽 

        sheet_1.addCell( new jxl.write.Label(3, 1, "prem" ,NormalTitle));               // 第 4 列列名 

        sheet_1.setColumnView(3,10);                                           // 第 4 列列宽 

        

        /* 插入图片 */ 

        //File imgFile = new File("D:/1.png"); 

        //insertImg(sheet_1,0,1,6,6,imgFile); 

         

  

        /* 数据库数据列表导出 */ 

            ArrayList array = new Basic().Test(); 

            HashMap hash; 

            for ( int i = 0;i&lt;array.size();i++) 

            { 

              hash = (HashMap) array.get(i); 

              String str1 = (String) hash.get( "polno" ); 

              String str2 = (String) hash.get( "contno" ); 

              String str3 = (String) hash.get( "riskcode" ); 

              Double str4 = (Double) hash.get( "prem" ); 

  

              sheet_1.addCell( new jxl.write.Label (0, i+2, str1,Body));              // 第 1 列数据 

              sheet_1.addCell( new jxl.write.Label(1, i+2, str2,Body));         // 第 2 列数据 

              sheet_1.addCell( new jxl.write.Label(2, i+2, str3,Body));         // 第 3 列数据 

              sheet_1.addCell( new jxl.write.Number(3, i+2, str4,Body));             // 第 4 列数据                 

            } 

            

            // 写入数据并关闭文件 

        book.write();    

        book.close();      

        } 

             catch (Exception e)  {    

             System. out .println(e); 

             }    

        }    

        }</pre>
          <br/>
          <span style="color:red;">
            <a href="http://wing929.javaeye.com/blog/230513#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 20 Aug 2008 11:21:59 +0800</pubDate>
        <link>http://wing929.javaeye.com/blog/230513</link>
        <guid>http://wing929.javaeye.com/blog/230513</guid>
      </item>
      <item>
        <title>应用设计模式编写易于单元测试的代码</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/219090" style="color:red;">http://wing929.javaeye.com/blog/219090</a>&nbsp;
          发表时间: 2008年07月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          单元测试是软件开发的一个重要组成部分，通过在软件设计、开发的过程中合理地运用设计模式，不但为系统重构、功能扩展及代码维护提供了方便，同时也为单元测试的实施提供了极大的灵活性，可以有效降低单元测试编码的难度，更好地保证软件开发的质量。<br />引言<br /><br />    设计模式是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述，通过在系统设计中引入合适的设计模式可以为系统实现提供更大的灵活性，从而有效地控制变化，更好地应对需求变更或者按需变更系统运行路径等问题。<br /><br />    请访问 Java 设计模式专题，查看更多关于 Java 设计模式的文章和教程。<br /> <br />    单元测试是软件开发的一个重要组成部分，是与编码实现同步进行的开发活动，这一点已成为软件开发者的共识。适度的单元测试不但不会影响开发进度，反而可以为开发过程提供很好的控制，为软件质量、系统重构等提供有力的保障，并且，当后续系统需求发生变更、Bug Fix 或功能扩展时，能很好地保证已有实现不会遭到破坏，从而使得程序更易于维护和修改。 Martin Fowler、Kent Beck、Robert Martin 等软件设计领域泰斗更是极力倡导测试先行的测试驱动开发（Test Driven Development，TDD）的开发方式。<br /><br />    单元测试主要用于测试细粒度的程序单元，如类的某个复杂方法的正确性，也可以根据需要综合测试某个操作所涉及的多个相互联系的类的正确性。在很多情况下，相互联系的多个类中有些类比较简单，为这些简单类单独编写单元测试用例往往不如将它们与使用它们的类一起进行测试有意义。<br /><br />    模拟对象（Mock Objects）是为模拟被测试单元所使用的外围对象、设备（后文统一简称为外部对象）而设计的一种特殊对象，它们具有与外部对象相同的接口，但实现往往比较简单，可以根据测试的场景进行定制。由于单元测试不是系统测试，方便、快速地被执行是单元测试的一个基本要求，直接使用外部对象往往需要经过复杂的系统配置，并且容易出现与欲测试功能无关的问题；对于一些异常的场景，直接使用外部对象可能难以构造，而通过设计合适的 Mock Objects，则可以方便地模拟需要的场景，从而为单元测试的顺利执行提供有效的支持。<br /><br />    本文根据笔者经验，介绍了几种典型的设计模式在系统设计中的应用，及由此为编写单元测试带来的方便。<br /><br />从对象创建开始<br /><br />    由于需要使用 Mock Objects 来模拟外部对象的功能，因此必须修改正常的程序流程，使得被测试功能模块与 Mock Objects，而不是外部对象进行交互。要做到这一点，首先要解决的问题就是对象创建，即在原本创建外部对象的地方创建 Mock Objects，因此在设计、实现业务逻辑时需要注意从业务逻辑中分离出对象创建逻辑。<br /><br /> 关于 setUp<br /><br />    setUp 是 JUnit 基础类 TestCase 的一个重要方法，每个单元测试在被执行前会调用 setUp 方法做一些必要的预处理，如准备好一些公共的基本输入或创建所需的外部对象。 <br /> <br />    Factory Method 是一种被普遍运用的创建型模式，用于将对象创建的职责分离到独立的方法中，并通过子类化来实现创建不同对象的目的。如果被测试单元所使用的外部对象是通过 Factory Method 创建的，则可以通过从已有被测试的 Factory 类派生出一个新的 MockFactory，以创建 Mock Objects，并在 setUp 测试中创建 MockFactory，从而间接达到对被测试类进行测试的目的。<br /><br />下面的代码片段展示了具体的做法：<br /><br /><pre name="code" class="java">// BaseObjects.java
package com.factorymethod.demo;
public interface BaseObjects {
    voidfunc(); 
} 

// OuterObjects.java
package com.factorymethod.demo;
public class OuterObjects implements BaseObjects {
    public void func() { 
        System.out.println("OuterObjects.func"); 
    } 
} 

// LogicToBeTested.java, code to be tested
package com.factorymethod.demo;
public class LogicToBeTested {
    public void doSomething() { 
        BaseObjects b = createBase(); 
        b.func(); 
    }
    
    public BaseObjects createBase() {
        return newOuterObjects(); 
    } 
} </pre><br /><br />以下则是对应的 MockOuterObjects、MockFactory 以及单元测试的实现：<br /><br /><pre name="code" class="java">// MockOuterObjects.java
package com.factorymethod.demo;
public class MockOuterObjects implements BaseObjects {
    public void func() { 
        System.out.println("MockOuterObjects.func"); 
    } 
} 

// MockLogicToBeTested.java
package com.factorymethod.demo;
public class MockLogicToBeTested extends LogicToBeTested {
    public BaseObjects createBase() {
        return new MockOutterObjects(); 
    } 
} 

// LogicTest.java
package com.factorymethod.demo;
import junit.framework.TestCase;
 
public class  LogicTest extends TestCase { 
    LogicToBeTested c;
    protected void setUp() { 
        c =new MockLogicToBeTested(); 
    }
    public void testDoSomething() { 
        c.doSomething(); 
    } 
}</pre>			 <br /><br />    Abstract Factory 是另一种被普遍运用的创建型模式，Abstract Factory 通过专门的 Factory Class 来封装对象创建的职责，并通过实现 Abstract Factory 来完成不同的创建逻辑。如果被测试单元所使用的外部对象是通过 Abstract Factory 创建的，则实现一个新的 Concrete Factory，并在此 Factory 中创建 Mock Objects 是一个比较好的解决办法。对于 Factory 本身，则可以在 setUp 测试的时候指定新的 Concrete Factory ；此外，借助依赖注入框架（如 Spring 的 BeanFactory），通过依赖注入的方式将 Factory 注入也是一种不错的解决方案。对于简单的依赖注入需求，可以考虑实现一个应用专有的依赖注入模块，或者实现一个简单的实现加载器，即根据配置文件载入相应的实现，从而无需修改应用代码，仅通过修改配置文件即可载入不同的实现，进而方便地修改程序的运行路径，执行单元测试。<br /><br />下面的代码实现了一个简单的 InstanceFactory：<br /><br /><pre name="code" class="java">// refer to http://www.opensc-project.org/opensc-java/export/100/trunk/
// pkcs15/src/main/java/org/opensc/pkcs15/asn1/InstanceFactory.java
packagecom.instancefactory.demo;

importjava.lang.reflect.InvocationTargetException;
importjava.lang.reflect.Method;
importjava.lang.reflect.Modifier;

public class InstanceFactory {
    private final Method getInstanceMethod;
    
    public InstanceFactory(String type) { 
        Class clazz =null;
        try { 
            clazz = Class.forName(type);
            this.getInstanceMethod = clazz.getMethod("getInstance");
            if(!Modifier.isStatic(this.getInstanceMethod.getModifiers()) 
            || !Modifier.isPublic(this.getInstanceMethod.getModifiers()))
                throw new IllegalArgumentException(
                    "Method [" + clazz.getName() 
                    + ".getInstance(Object)] is not static public."); 
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException(
                "Class [" + clazz.getName() 
                + "] has no static getInstance(Object) method.", e); 
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Class [" + type + "] is not found"); 
        } 
    }

    public Object getInstance() {
        try{
            return this.getInstanceMethod.invoke(null); 
        } catch (InvocationTargetException e) {
            if( e.getCause() instanceof RuntimeException )
                throw (RuntimeException)e.getCause();
            throw new IllegalArgumentException(
                    "Method [" +this.getInstanceMethod 
                    + "] has thrown an checked exception.", e); 
        } catch( IllegalAccessException e) {
            throw new IllegalArgumentException(
                    "Illegal access to method [" 
                    +this.getInstanceMethod + "].", e); 
        } 
    }
    
    public Method getGetInstanceMethod() {
        return this.getInstanceMethod; 
    } 
} </pre><br /><br />以下代码演示了 InstanceFactory 的简单使用：<br /><br /><pre name="code" class="java">// BaseObjects.java
package com.instancefactory.demo;

public interface BaseObjects {
    voidfunc(); 
} 

 // OuterObjects.java

package com.instancefactory.demo;

public class OuterObjects implements BaseObjects {
    public static BaseObjects getInstance() {
        return new OuterObjects(); 
    }
    
    public void func() { 
        System.out.println("OuterObjects.func"); 
    } 
} 

// MockOuterObjects.java
package com.instancefactory.demo;
public class MockOuterObjects implements BaseObjects {
    public static BaseObjects getInstance() {
        return new MockOuterObjects(); 
    }
    
    public void func() { 
        System.out.println("MockOuterObjects.func"); 
    } 
 } 

// LogicToBeTested.java
packagecom.instancefactory.demo;
public class LogicToBeTested {
    public static final String PROPERTY_KEY= "BaseObjects";
    public void doSomething() { 
        // load configuration file and read the implementation class name of BaseObjects 
        // read it from properties to simplify the demo 
        // actually, the property file reader can be implemented by InstanceFactory 
        String impl = System.getProperty(PROPERTY_KEY); 
        InstanceFactory factory = new InstanceFactory(impl); 
        BaseObjects b = (BaseObjects)factory.getInstance(); 
        b.doSomething(); 
    } 
 } 

// LogicTest.java
packagecom.instancefactory.demo;
importjunit.framework.TestCase;
public class LogicTest extends TestCase { 
    LogicToBeTested c;
    protected void setUp() { 
        // set the property file of class map to a file for MockObjects, omitted 
        // use System.setProperty to simplify the demo 
        System.setProperty(LogicToBeTested.PROPERTY_KEY, 
                "com.instancefactory.demo.MockOuterObjects"); 
        c = new LogicToBeTested(); 
    }
    
    public void testDoSomething() { 
        c.doSomething(); 
    } 
 } </pre><br /><br />替换实现<br /><br />    通过 Factory Method 替换被创建对象可以满足一些修改程序运行路径的需求，但是，这种方法以子类化为前提，具有很强的侵入性，并且在编写单元测试时，开发人员需要同时负责 Mock Objects 的开发，供 Factory Method 调用，因此，编码量往往会比较大，单元测试开发人员也需对所使用的公共模块的内部结构有十分清楚的认识。即使可以使用公共的 Mock Objects 实现避免代码重复，往往也需要修改业务逻辑中公共服务相关对象的创建代码，这一点对于应用公共模块的业务逻辑的单元测试可能不太适合。<br /><br />    在笔者曾参与设计、开发的某应用系统中，有一个专门的数据库缓冲（Cache）公共服务，该 Cache 负责完成与数据库交互，实现数据的存取，并缓存数据以提高后续访问的效率。对于涉及数据库缓冲的业务逻辑的单元测试，需要一个替代方案来替代已有的数据库缓冲，以避免直接访问实际数据库，但又要保证这个替换不会影响到被测试单元的实现。<br /><br />    为了解决这个问题，我们并没有直接替换 Cache 创建处的代码，因为这些代码遍布在业务代码中，直接替换 Cache 创建代码无疑会侵入业务逻辑，并需要大量使用子类化。为了尽可能降低对业务逻辑的影响，我们维持了原有 CacheFactory 的接口，但是将 CacheFactory 的实现委托（Delegate）给另一个实现类完成，以下是 CacheFactory 实现的伪代码：<br /><br /><pre name="code" class="java">package com.cachefactory.demo;
public abstract class CacheFactory {
    private static CacheFactoryinstance = new DelegateCacheFactory();
    private static CacheFactorydelegate;
    protected CacheFactory() { 
    } 
  
    // CacheFactory is a singletonpublic
    static CacheFactory getInstance() {
        return instance; 
    } 
  
    // the implementation can be changedprotected
    static void setDelegate(CacheFactory instance) {
        delegate= instance; 
    }
        
    public abstract Cache getCache(Object... args); 
 
    // redirect all request to delegateeprivate
    static class DelegateCacheFactoryextendsCacheFactory {
        private DelegateCacheFactory() { 
        }
            
        public Cache getCache(Object... args) {
            return delegate.getCache(args); 
        } 
    } 
 } </pre><br /><br />    与 CacheFactoryImpl 类似地，我们实现了一个 MockCacheFactory，但与 CacheFactoryImpl 不同的是，这个 MockCacheFactory 所创建的 MockCache 对象虽然与真正的 Cache 实现了相同的接口，但是，它的内部实现却是基于 HashMap 的，因此，可以很好地满足单元测试快速、方便地运行的需要。<br /><br />    单元测试时，只需要在 setUp 时调用执行如下操作：<br /><br /><pre name="code" class="java">setDelegate(new MockCacheFactory()); </pre><br /><br /><br />    将 CacheFactory 的实现委托给 MockCacheFactory 即可，所有业务逻辑都无需作任何修改，因此，这种替换实现的方式几乎是没有侵入性的。<br /><br />    这种通过将实现分离到专门的实现类中的做法其实是 Bridge 模式的一个应用，通过使用 Bridge 模式，为替换实现保留了接口，从而使得在不对业务逻辑作任何修改的情况下可以轻松替换公共服务的实现。<br /><br />    除此之外，Strategy 模式也是一种替换实现的有效途径，这种方式与 Factory Method 类似，通过子类化实现新的 Strategy 以替换业务逻辑使用的旧的 Strategy，通过与 Factory Method 或 Bridge 等模式联合使用，在编写应用公共服务的业务逻辑的单元测试时也十分有用。<br /><br />绕过部分实现<br /><br />    绕过部分实现进行单元测试在大多数情况下是不可取的，因为这种做法极有可能会影响单元测试的质量。但是对于一些特殊的情况，我们可以“冒险”使用这种方式，比如有这样的一个场景：所有请求需经过多级认证，且部分认证处理需要访问数据库，认证结束后为请求分配相应的 sessionId，请求在获得 sessionId 后继续进行进一步的业务逻辑处理。<br /><br />    在保证多级认证模块已被专门的单元测试覆盖的情况下，我们在为业务逻辑编写单元测试的过程中可以考虑跳过多级认证授权模块（对于部分特权用户，也应跳过部分检查），直接为其分配一个 Mock 的 sessionId，以进行后续处理。<br /><br />    对于多级认证问题本身，我们可以考虑采用 Chain of Responsibility 模式将不同的认证逻辑封装到不同的 RequestHandler 中，并通过编码或者根据配置，将所有的 Handler 串联成 Responsibility Chain ；而在单元测试过程中，可以修改 Handler 的串联方式，绕过部分不希望在单元测试中经过的 Handler，从而简化单元测试的运行。<br /><br />    对于这个问题，笔者并不同意为了单元测试的需要去采用 Chain of Responsibility 模式，实际上，上面所阐述的多级认证问题本身比较适合采用这种模式来解决，能够根据需要绕过部分实现，只是应用这种模式的情况下进行单元测试的一种可以考虑的测试途径。<br /><br />总结<br /><br />    单元测试是软件开发的重要组成部分，而应用 Mock Object 是进行单元测试一种普遍而有效的方式，通过在软件设计、开发的过程中合理地运用设计模式，不但为系统重构、功能扩展及代码维护提供了方便，同时也为单元测试的实施提供了极大的灵活性，可以有效降低单元测试编码的难度，方便地在单元测试中引入 Mock Objects，达到对被测试目标进行单元测试的目的，从而更好地保证软件开发的质量。
          <br/>
          <span style="color:red;">
            <a href="http://wing929.javaeye.com/blog/219090#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 24 Jul 2008 15:14:55 +0800</pubDate>
        <link>http://wing929.javaeye.com/blog/219090</link>
        <guid>http://wing929.javaeye.com/blog/219090</guid>
      </item>
      <item>
        <title>怎样使用Junit Framework进行单元测试的编写</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/219086" style="color:red;">http://wing929.javaeye.com/blog/219086</a>&nbsp;
          发表时间: 2008年07月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          您的传统代码是不是要求使用匹配的类测试套件才能针对其源代码库运行？针对此类目的，jMock 堪称是一个优秀的测试框架。但是，并不是所有情况都能够适用，尤其是必须以 jMock 不期望的方式构造对象时。为避免生成自定义模拟对象套件才能支持应用程序中的单元测试的麻烦，可以调整 RMock，与 jMock 无缝地结合使用，从而解决这一问题。<br />    模拟对象将模仿出于指导代码执行的惟一目的而编写的类的行为，以便它在测试时符合代码执行要求。最终，模拟对象数目可以随着应用程序类数目的增长而增长。使用 jMock、RMock 甚至 EasyMock 等框架有助于消除对物理的独立存在的模拟对象集的需求。<br /><br />    EasyMock 框架的一个主要缺点是不能模拟具体类 —— 而只能模拟接口。在本文中，我将向您展示怎样使用 jMock 框架来模拟具体类和 接口，以及如何用 RMock 测试某些模糊的情况。<br /><br /> 注<br /><br />   Eclipse 平台为使用 jMock 和 RMock 测试框架提供了一种易于使用的机制。<br /> <br />    在 Eclipse IDE 中配置 jMock 和 RMock<br /><br />   注：有关 JUnit、jMock 和 RMock 的最新二进制文件，请参阅 参考资料。<br /><br />    首先启动 Eclipse 集成开发环境 (IDE)。接下来，创建一个基本 Java™ 项目，稍后将把 JUnit、jMock 和 RMock Java Archive (JAR) 库导入到该项目中。将 Java 项目命名为 TestingExample。在 Java Perspective 内，选择 Project > Properties，然后单击 Libraries 选项卡，如下所示：<br /><br />    图 1. 在 Eclipse 中编辑 TestingExample 项目的属性<br /> <img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure1.jpg" /><br /><br /><br />    当 JAR 文件位于 Java 类路径（即，已在 Eclipse 内配置的 Java 运行时环境（Java Runtime Environment，JRE））中时，请使用 Add JARs 按钮。Add Variable 按钮适用于文件系统（本地或远程）中的资源（包括 JAR）所驻留的具体目录，并且通常可以引用此按钮。在必须引用 Eclipse 中默认的那些特定资源或为特定的 Eclipse 工作区环境配置的那些特定资源时，请使用 Add Library 按钮。单击 Add Class Folder，从已经配置为项目一部分的一个现有项目文件夹中添加资源。<br /><br />    对于本示例，请单击 Add External JARs 并浏览到已下载的 jMock 和 RMock JAR。将其添加到项目中。当显示图 2 中所示的属性窗口时，请单击 OK。<br /><br />    图 2. 已添加到 TestingExample 项目中的 jMock 和 RMock JAR<br /> <img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure2.jpg" /><br /> <br />TestExample 源代码<br /><br />对于 TestExample 项目，您将使用来自四个类的源代码：<br /><br />ServiceClass.java <br />Collaborator.java <br />ICollaborator.java <br />ServiceClassTest.java <br />    待测试的类将是 ServiceClass，该类包含了一个方法：runService()。服务方法将获取实现简单接口 ICollaborator 的 Collaborator 对象。具体的 Collaborator 类中实现了一个方法：executeJob()。Collaborator 是必须正确模拟的类。<br /><br />    第四个类是测试类：ServiceClassTest（实现的性质已经被尽可能地简化）。清单 1 将显示第四个类的代码。<br /><br />    清单 1. 服务类的样例代码<br />                <br /><pre name="code" class="java">public class ServiceClass {
	public ServiceClass(){
	//no-args constructor	
	}

	public boolean runService(ICollaborator collaborator){
	if("success".equals(collaborator.executeJob())){
		return true;
	}
	else
	{
		return false;
	}
}
}</pre> <br /><br />在 ServiceClass 类中，if...else 代码块是一个简单的逻辑分支，根据测试期望说明选取一条路经 —— 而不是另一条路经 —— 之后测试将失败（或通过）的原因。下面显示了 Collaborator 类的源代码。<br /><br />清单 2. Collaborator 类的样例代码<br />                <br /><pre name="code" class="java">public class Collaborator implements ICollaborator{
   public Collaborator(){
	   //no-args constructor
   }
   public String executeJob(){
	   return "success";
   }
}</pre> <br /><br />Collaborator 类也十分简单，它配有无参数的构造函数以及从 executeJob() 方法返回的简单 String。下面的代码显示了 ICollaborator 类的代码。<br /><br /><pre name="code" class="java">public interface ICollaborator {
    public abstract String executeJob();
}</pre><br /> <br /><br /><br />接口 ICollaborator 有一个必须在 Collaborator 类中实现的方法。<br /><br />以上代码就绪后，让我们继续检验怎样在各种场景中成功地运行 ServiceClass 类的测试。<br /><br />场景 1：使用 jMock 模拟接口<br /><br />    测试 ServiceClass 类中的服务方法十分简单。假定测试要求为证明 runService() 方法并未运行 —— 换言之，返回的布尔结果是 false。在这种情况下，传递给 runService() 方法的 ICollaborator 对象被模拟 为期望调用 executeJob() 方法，并返回除了 “success” 以外的字符串。通过这种方法，确保把布尔字符串 false 返回给测试。<br /><br />下面所示的是包含测试逻辑的 ServiceClassTest 类代码。<br /><br /><br />清单 3. 场景 1 的 ServiceClassTest 类样例代码<br />                <br /><pre name="code" class="java">import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
public class ServiceClassTest extends MockObjectTestCase {
	private ServiceClass serviceClass;
	private Mock mockCollaborator;
	private ICollaborator collaborator;
	
	public void setUp(){
		serviceClass = new ServiceClass();
		mockCollaborator = new Mock(ICollaborator.class);
	}
	
	public void testRunServiceAndReturnFalse(){
		mockCollaborator.expects(once()).method\
              ("executeJob").will(returnValue("failure"));
		collaborator = (ICollaborator)mockCollaborator.proxy();
		boolean result = serviceClass.runService(collaborator);
		assertFalse(result);
	}
}</pre> <br /><br /> 编写测试的时机<br /><br />    用测试模拟框架运行您自己的测试的最佳方法是利用 test-first 灵活方法。首先创建测试并设定期望。仅在测试失败后才编写实现以修正测试。当测试运行正常时，您将编写另一个测试以检查稍后添加到待测试的类中的功能。<br /> <br /> <br />    如果将在各种测试用例中执行公共操作，则在测试中包括 setUp() 方法是一种很好的想法。包括 tearDown() 方法也很不错，但不作严格要求，除非要运行集成测试。<br /><br />    另请注意，使用 jMock 和 RMock，框架将在测试运行结束时或测试运行期间在所有模拟对象中检查所有期望。并不实际需要为每个模拟期望包括 verify() 方法。当作为 JUnit 测试运行时，测试将通过，如下所示：<br /><br /><br />图 3. 场景 1 测试通过<br /><img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure3.jpg" /> <br /><br /><br />ServiceTestClass 类将扩展 jMock CGLIB 的 org.jmock.cglib.MockObjectTestCase 类。mockCollaborator 是一个十分简单的 org.jmock.JMock 类。通常，用 jMock 生成模拟对象有两种方法：<br /> <br />    要模拟接口，则使用 new Mock(Class.class) 方法 <br />    要模拟具体类，则使用 mock(Class.class, "identifier") 方法 <br />必须注意的是怎样将模拟代理 传递给 ServiceClass 类中的 runService() 方法。使用 jMock，您可以从已创建的模拟对象（其中期望已经被设定）中提取代理实现。这一点在本文稍后的场景中至关重要，尤其是在涉及 RMock 的场景中。<br /><br /><br />场景 2：使用 jMock 模拟带有默认构造函数的具体类<br /><br />    假定 ServiceClass 类中的 runService() 方法仅接受 Collaborator 类的具体实现。jMock 能够确保先前的测试通过而无需 更改期望吗？是的，只要您能够构造简单默认样式的 Collaborator 类。<br /><br />    更改 ServiceClass 类中的 runService() 方法使其反映以下代码。<br /><br />清单 4. 经过编辑的场景 2 的 ServiceClass 类<br />                <br /><pre name="code" class="java">public class ServiceClass {
	public ServiceClass(){
	//no-args constructor	
	}

public boolean runService(Collaborator collaborator){
	if("success".equals(collaborator.executeJob())){
		return true;
	}
	else{
		return false;
	}
}
}</pre> <br /><br />    ServiceClass 类的 if...else 逻辑分支保持不变（为了清晰起见）。同时，无参数构造函数仍然适用。注，并不总是需要有创造性逻辑，例如 while...do 子句或 for 循环来正确地测试类的方法。只要有针对类使用的对象的方法执行，简单的模拟期望就足以测试那些执行。<br /><br />您还必须更改 ServiceClassTest 类以匹配场景，如下所示：<br /><br /><br />清单 5. 经过编辑的场景 2 的 ServiceClassTest 类<br /><pre name="code" class="java">private ServiceClass serviceClass;
	private Mock mockCollaborator;
	private Collaborator collaborator;
	
	public void setUp(){
		serviceClass = new ServiceClass();
		mockCollaborator = mock(Collaborator.class, "mockCollaborator");
	}
	
	public void testRunServiceAndReturnFalse(){
		mockCollaborator.expects(once()).method("executeJob").will(returnValue("failure"));
		collaborator = (Collaborator)mockCollaborator.proxy();
		boolean result = serviceClass.runService(collaborator);
		assertFalse(result);
	}
}</pre> <br /><br />    这里有几点需要注意。第一，runService() 方法签名已经不同于以往。它现在不接受 ICollaborator 接口，而接受具体类实现（Collaborator 类）。就测试框架而言，此更改非常重大（注，虽然在本质上反对多态，但是我们将使用传递具体类的示例（仅供举例之用）。在实际的面向对象的场景中绝对不能这样做）。<br /><br />    第二，模拟 Collaborator 类的方式已经更改。使用 jMock CGLIB 库可以模拟具体类实现。提供给 jMock CGLIB 的 mock() 方法的附加 String 参数被用作创建的模拟对象的标识符。使用 jMock（当然，还有 RMock）时，在单一测试用例内每个模拟对象设置都要求有惟一标识符。这对于在公共的 setUp() 方法中或在实际测试方法内定义的模拟对象来说是正确的。<br /><br />    第三，测试方法的原始期望并未更改。仍然要求有 false 证明才能使测试通过。这是十分重要的，因为通过展示使用的测试框架足够灵活、可以适应各种输入带来的更改、同时仍然允许获得不变的测试结果，使它们在无法调节输入生成同样的结果时展示了其实际限制。<br /><br />    现在，重新运行作为 JUnit 测试的测试。测试将通过，如下所示：<br /><br />图 4. 场景 2 测试通过<br /><img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure4.jpg" /> <br /><br />    在下一个场景中，情况会变得略微复杂一些。您将使用 RMock 框架来相对缓解一下这种困难的情形。<br /><br />场景 3：使用 jMock 和 RMock 模拟带有非默认构造函数的具体类<br /><br />    首先像以前一样尝试使用 jMock 来模拟 Collaborator 对象 —— 只是这一次，Collaborator 没有默认的无参数构造函数。注，保留布尔 false 结果的测试期望。<br /><br />    同时假定 Collaborator 对象要求使用字符串和原始的 int 作为传递给构造函数的参数。清单 6 显示了对 Collaborator 对象所做的更改。<br /><br />清单 6. 经过编辑的场景 3 的 Collaborator 类<br />                <br /><pre name="code" class="java">public class Collaborator{
   private String collaboratorString;
   private int collaboratorInt;
	
   public Collaborator(String string, int number){
	   collaboratorString = string;
	   collaboratorInt = number;
   }
   public String executeJob(){
   	return "success";
  }
}</pre> <br /><br />    Collaborator 类构造函数仍然十分简单。用传入参数设定类字段。这里不必使用任何其他逻辑，并且其 executeJob() 函数保持不变。<br /><br />    重新运行测试，并且示例的所有其他组件保持不变。结果是灾难性的测试失败，如下所示：<br /><br />图 5. 场景 3 测试失败<br /><img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure5.jpg" /> <br /><br />    以上测试是作为简单的 JUnit 测试运行的，没有代码覆盖。您可以用大多数代码覆盖工具（例如，Cobertura 或 EclEmma）来运行本文中列出的任何一个测试。但是，用 Eclipse 内的代码覆盖工具运行 RMock 测试时会带来一些问题（参见 表 1）。以下代码显示了实际堆栈跟踪的代码片段。<br /><br />清单 7. 场景 3 中测试失败的堆栈跟踪<br />                <br /><div class="quote_title">引用</div><div class="quote_div">                ...Superclass has no null constructors but no arguments were given<br />	at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:718)<br />	at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)<br />	at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)<br />	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)<br />	at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)<br />	at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)<br />	at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:660)<br />	.....<br />	.....</div> <br /><br />    失败原因是 jMock 无法通过没有无参数构造函数的类定义创建可行的模拟对象。实例化 Collaborator 对象的惟一方法是提供两个简单参数。您现在必须找到一种方法把参数提供给模拟对象实例化过程以达到同样的效果，这就是使用 RMock 的原因。<br /><br />用 RMock 测试框架更正失败的测试<br /><br />    要更正测试，必须执行一些修改。这些更改可能显得十分重要，但是实际上，它们是一种相对简单的解决方法，利用两种框架的强大功能来实现目的。<br /><br />    必需的第一项更改是使测试类成为 RMock TestCase，而不是成为 jMock CGLIB TestCase。目的是在测试本身内启用属于 RMock 的那些模拟对象的较容易的配置并且 —— 更重要的是 —— 在最初设置期间启用这些配置。经验证明，如果测试类扩展的整个 TestCase 对象属于 RMock，则通过两个框架构造和使用模拟对象将更容易。此外，乍看之下，快速确定模拟对象的流程将更容易一些（在这里，流程 用于描述使用模拟对象作为参数甚或作为其他模拟对象的返回类型的情况）。<br /><br />    必需的第二项更改是（至少）构造一个保存传递给 Collaborator 类的构造函数的参数实际值的对象数组。为了清晰起见，还可以包括构造函数接受的类类型的类型数组并可以传递该数组，以及刚刚描述为参数的对象数组以实例化模拟 Collaborator 对象。<br /><br />    第三项更改涉及用正确语法构造对 RMock 模拟对象的一个或多个期望。而第四项也是最后一项必需的更改是使 RMock 模拟对象脱离记录状态转入就绪状态。<br /><br />实现 RMock 更改<br /><br />    清单 9 显示了对 ServiceClassTest 类的最终修改。它还显示了 RMock 及其相关功能的引入。<br /><br /><br />清单 9. 修正场景 3 的 ServiceClassTest 类<br />                <br /><pre name="code" class="java">import com.agical.rmock.extension.junit.RMockTestCase;
public class ServiceClassTest extends RMockTestCase {

	private ServiceClass serviceClass;
	private Collaborator collaborator;
	
	public void setUp(){
		serviceClass = new ServiceClass();
		Object[] objectArray = new Object[]{"exampleString", 5};
                collaborator =
                (Collaborator)intercept(Collaborator.class, objectArray, "mockCollaborator");
	}
	
	public void testRunServiceAndReturnFalse(){
		collaborator.executeJob();
		modify().returnValue("failure");
		startVerification();
		boolean result = serviceClass.runService(collaborator);
		assertFalse(result);
	}
}</pre> <br /><br />    首先，需要注意测试的期望仍未改变。RMockTestCase 类的导入预示着引入 RMock 框架功能。接下来，测试类现在将扩展 RMockTestCase，而不是 MockObjectTestCase。稍后，我将向您展示在 TestClass 对象仍为 RMockTestCase 类型的对象的测试用例中重新引入 MockObjectTestCase。<br /><br /> 使用 intercept() 方法的备选方法<br /><br />    通过 RMock，您可以使用 intercept() 方法仅模拟具体类。可以使用 RMock mock() 方法模拟具体类和接口。仅当需要模拟那几个重要方法时，使用 interface() 方法。此方法被视为经过改进的 mock() 方法。<br /> <br />    在 setUp() 方法内，用 Collaborator 类的构造方法所需的实际 值实例化对象数组。该数组被立刻传递给 RMock 的 intercept() 方法来帮助实例化模拟对象。方法的签名类似于 jMock CGLIB mock() 方法的签名，因为这两种方法将接纳惟一模拟对象标识符作为参数。模拟对象到 Collaborator 类型的类强制转换十分有必要，因为 intercept() 方法将返回类型 Object。<br /><br />    在测试方法本身 testRunServiceAndReturnFalse() 之内，您可以看到更多更改。模拟 Collaborator 对象的 executeJob() 方法将被调用。在此阶段，模拟对象处于记录状态 —— 即简单地定义对象将一直期望的方法调用，因此，模拟将相应地记录期望。下一行是对模拟对象的通知，用于确保当它遇到 executeJob() 方法时，它应当返回字符串 failure。因此，使用 RMock，您只需通过调用方法而不调用模拟对象（并传递它可能需要的任何参数）来描述期望，然后修改该期望以相应地调整任何返回类型。<br /><br />    最后，调用 RMock 方法 startVerification() 把模拟 Collaborator 对象转为就绪状态。模拟对象现已准备好在 ServiceClass 类中作为实际对象使用。该方法非常重要并且必须调用它才能避免测试初始化失败。<br /><br />测试更改<br /><br />    再次重新运行 ServiceClassTest 以达到最终的肯定结果：在模拟对象实例化期间提供的参数造成了所有差别。图 6 显示 JUnit 测试已经通过。<br /><br /><br />图 6. 使用 RMock 的场景 3 测试成功<br /><img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure6.jpg" /> <br /><br />    assertFalse(result) 代码行表示与场景 1 相同的测试期望，而 RMock 像 jMock 以前那样维持测试成功。在许多方面，这都十分重要，但是这里更重要的特点可能是实践了修正失败测试的灵活 原则而不更改测试期望。惟一的差别是使用了另一个框架。<br /><br />    在下一个场景中，您将在一种特殊情况下使用 jMock 和 RMock。没有一个框架能够仅凭自身就实现正确结果，除非在测试内形成某种联合。<br /><br />场景 4：jMock 和 RMock 之间的特定协作<br /><br />    如前所述，我希望检验两个框架必须协同工作才能实现某个结果的情况。否则，构建良好的测试每次都将失败。在某些情况下，使用 jMock 还是 RMock 并不重要，例如，当需要模拟的接口或类存在于已经签名的 JAR 中时。此类情况十分少见，但是当测试针对安全专有的产品（通常是这样或那样的一类现有软件）中的应用程序编程接口 (API) 编写代码时可能会出现此情况。<br /><br />清单 10 显示了两个框架完成测试用例的示例。<br /><br /><br />清单 10. 场景 4 的测试示例<br />                <br /><pre name="code" class="java">public class MyNewClassTest extends RMockTestCase{

private MyNewClass myClass;
private MockObjectTestCase testCase;
private Collaborator collaborator;
private Mock mockClassB;

    public void setUp(){
        myClass = new MyNewClass();

        testCase = new MyMockObjectTestCase();

        mockClassB = testCase.mock(ClassB.class, "mockClassB");
 		mockClassB.expects(testCase.once()).method("wierdMethod").
                will(testCase.returnValue("passed"));

        Class[] someClassArray = new Class[]{String.class, ClassA.class, ClassB.class};
        Object[] someObjectArray = new Object[]
        	{"someArbitraryString", new ClassA(), (ClassB)mockClassB.proxy()};

        collaborator = (Collaborator)intercept
                (Collaborator.class, someClassArray, someObjectArray, "mockCollaborator");
    }

    public void testRMockAndJMockInCollaboration(){
        startVerification();
        assertTrue(myClass.executeJob(collaborator));
    }

    private class MyMockObjectTestCase extends MockObjectTestCase{}

    private class MyNewClass{
        public boolean executeJob(Collaborator collaborator){
            collaborator.executeSomeImportantFunction();
            return true;
        }
    }
}</pre> <br /><br />    在 setUp() 方法内，根据为扩展 jMock-CGLIB MockObjectTestCase 对象而创建的私有内部类实例化了新 "testcase"。使用任何 jMock 功能时，这个小解决方法对于确保整个测试类为 RMock TestCase 对象十分有必要。例如，您将设定类似 testCase.once() 而不是类似 once() 的 jMock 期望，因为 TestClass 对象将扩展 RMockTestCase。<br /><br />    构建基于 ClassB 类的模拟对象并向其提供期望。然后您将使用它帮助实例化 RMock Collaborator 模拟对象。待测试的类是 MyNewClass 类（在这里显示为私有内部类）。同时，其 executeJob() 方法将接收 Collaborator 对象并运行 executeSomeImportantFunction() 方法。<br /><br />    清单 11 和 12 分别显示了 ClassA 和 ClassB 的代码。ClassA 是没有实现的简单类，而 ClassB 显示了阐明要点所需的最少细节。<br /><br /><br />清单 11. ClassA 类<br />                <br /><pre name="code" class="java">public class ClassA{}</pre><br /> <br />    此类只是我使用的一个虚构类，用于强化一个要点：要模拟构造函数接收对象参数的类，有必要使用 RMock。<br /><br />清单 12. ClassB 类<br />                <br /><pre name="code" class="java">public class ClassB{
    	public ClassB(){}
        public String wierdMethod(){
            return "failed";
        }
    }</pre> <br /><br />    ClassB 类的 wierdMethod 将返回 failed。这是十分重要的，因为该类必须简短地返回另一个字符串才能使测试通过。<br /><br />清单 13 显示了测试示例的最重要部分：Collaborator 类。<br /><br />清单 13. Collaborator 类<br />                <br /><pre name="code" class="java">public class Collaborator {
	private String  _string;
    private ClassA _classA;
    private ClassB _classB;

    public Collaborator(String string, ClassA classA, ClassB classB) throws Exception{
         _string = string;
        _classA = classA;
        if(classB.wierdMethod().equals("passed")){
            _classB =classB;
        }
        else{
                throw new Exception("Something bad happened"); 
        }
    }

    public void executeSomeImportantFunction(){
    }
}</pre> <br /><br />    注，首要的是，使用 jMock 框架模拟了 ClassB 类。使用 RMock，没有一种实际方法从模拟对象中提取和使用代理，以便在测试 setUp() 方法中的其他位置使用该代理。使用 RMock，仅当调用 startVerification() 方法之后，才显示代理对象。本例中的优点是使用 jMock，因为在需要返回自我模拟对象的情况下，可以 获得设置其他模拟对象所需的信息。<br /><br />    反过来，需要注意的第二点是您不能使用 jMock 框架来模拟 Collaborator 类。原因是该类没有无参数构造函数。此外，在构造函数内有某种逻辑，这种逻辑将确定是否先获得实例。事实上，出于本次讨论的目的，ClassB 中的 wierdMethod() 方法必须返回 passed 才能使 Collaborator 对象被实例化。但是，另请注意，在默认情况下，方法总是返回 failed。测试成功明显需要模拟 ClassB。<br /><br />    此外，不同于先前的示例，此场景中的类数组作为附加参数被包含到了 intercept() 方法中。对此不作严格要求，但是用它作为密钥可以快速识别在实例化 RMock 测试对象时使用的对象类。<br /><br />    运行新测试用例。这一次，您将看到成功的结果。图 7 将显示令人愉快的结果。<br /><br /><br />图 7. RMock 与 jMock 协作使场景 4 测试成功<br /><img src="http://www.ibm.com/developerworks/cn/opensource/os-eclipse-rmock/Figure7.jpg" /> <br /><br />    Collaborator 模拟对象已被正确设置，并且 mockClassB 对象将按预期执行。<br /><br /><br />快速查看测试工具差别<br /><br />    正如您已经在场景中看到的，jMock 和 RMock 都是用于测试 Java 代码的强大工具。但是，用于开发和测试的任何其他工具总是有限制。实际上，其他测试工具都是可用的，但是这些测试工具的运行情况都不如 RMock 和 jMock（在 Java 技术中）。个人经验告诉我 Microsoft® .NET 框架也附带了一些功能强大的工具（例如 TypeMock），但是那超出了本文（实际上还有平台）的范围。<br /><br />    表 1 显示了两个框架之间的一些不同之处以及随着时间的推移遇到的可能问题，尤其是在 Eclipse 环境中。<br /><br /><br />表 1. RMock 与 jMock 测试框架之间的不同之处<br />测试模拟样式            jMock                          RMock <br />可以模拟接口            是：新的 Mock() 方法               是：mock() 方法 <br />可以模拟具体类          是：带有 CGLIB 的 mock() 方法     是：mock() 或 intercept() 方法 <br />可以模拟任何具体类      否：无参数构造函数必须存在          是 <br />可以随时获得代理        是                                  否：仅当 startVerification() 处于就绪状态后<br />使用其他Eclipse插件的问题无已知问题 是：与 Eclipse 的 CoverClipse 插件存在内存冲突 <br /><br />结束语<br /> <br />    我鼓励您使用这些框架，利用它们的力量来生成单元测试的结果。许多 Java 开发人员不习惯于频繁编写测试。而且如果需要编写测试，通常都是十分简单、覆盖方法的主要功能目标的测试。要测试代码的某些 “难以达到的” 部分，jMock 和 RMock 都是优秀的选择。<br /><br />    使用 jMock 和 RMock 将极大地减少代码中的 bug，提高使用经过证明的方法测试编程逻辑的技巧。此外，阅读文档并用这些框架和其他框架的改进版本进行测试（并减少构造不好的代码）将对提高开发人员技巧有着额外的帮助。
          <br/>
          <span style="color:red;">
            <a href="http://wing929.javaeye.com/blog/219086#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 24 Jul 2008 15:10:58 +0800</pubDate>
        <link>http://wing929.javaeye.com/blog/219086</link>
        <guid>http://wing929.javaeye.com/blog/219086</guid>
      </item>
      <item>
        <title>单元测试利器 JUnit 4</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/219065" style="color:red;">http://wing929.javaeye.com/blog/219065</a>&nbsp;
          发表时间: 2008年07月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          本文主要介绍了如何使用 JUnit 4 提供的各种功能开展有效的单元测试，并通过一个实例演示了如何使用 Ant 执行自动化的单元测试。本文假设读者对 Eclipse 下进行 Java 开发有一定的经验，并了解 Java 5 中的注解（annotation）特性。<br />引言<br /><br />    毋庸置疑，程序员要对自己编写的代码负责，您不仅要保证它能通过编译，正常地运行，而且要满足需求和设计预期的效果。单元测试正是验证代码行为是否满足预期的有效手段之一。但不可否认，做测试是件很枯燥无趣的事情，而一遍又一遍的测试则更是让人生畏的工作。幸运的是，单元测试工具 JUnit 使这一切变得简单艺术起来。<br /><br />    JUnit 是 Java 社区中知名度最高的单元测试工具。它诞生于 1997 年，由 Erich Gamma 和 Kent Beck 共同开发完成。其中 Erich Gamma 是经典著作《设计模式：可复用面向对象软件的基础》一书的作者之一，并在 Eclipse 中有很大的贡献；Kent Beck 则是一位极限编程（XP）方面的专家和先驱。<br /><br />    麻雀虽小，五脏俱全。JUnit 设计的非常小巧，但是功能却非常强大。Martin Fowler 如此评价 JUnit：在软件开发领域，从来就没有如此少的代码起到了如此重要的作用。它大大简化了开发人员执行单元测试的难度，特别是 JUnit 4 使用 Java 5 中的注解（annotation）使测试变得更加简单。<br /><br />JUnit 4 初体验<br /><br />    在开始体验 JUnit 4 之前，我们需要以下软件的支持：<br /><br />    Eclipse：最为流行的 IDE，它全面集成了 JUnit，并从版本 3.2 开始支持 JUnit 4。当然 JUnit 并不依赖于任何 IDE。您可以从 http://www.eclipse.org/ 上下载最新的 Eclipse 版本。 <br />    Ant：基于 Java 的开源构建工具，您可以在 http://ant.apache.org/ 上得到最新的版本和丰富的文档。Eclipse 中已经集成了 Ant，但是在撰写本文时，Eclipse 使用的 Ant 版本较低（必需 1.7 或者以上版本），不能很好的支持 JUnit 4。 <br />    JUnit：它的官方网站是 http://www.junit.org/。您可以从上面获取关于 JUnit 的最新消息。如果您和本文一样在 Eclipse 中使用 JUnit，就不必再下载了。 <br />    首先为我们的体验新建一个 Java 工程 —— coolJUnit。现在需要做的是，打开项目 coolJUnit 的属性页 -> 选择“Java Build Path”子选项 -> 点选“Add Library…”按钮 -> 在弹出的“Add Library”对话框中选择 JUnit（图1），并在下一页中选择版本 4.1 后点击“Finish”按钮。这样便把 JUnit 引入到当前项目库中了。<br /><br /><br />图1 为项目添加 JUnit 库<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure001.jpg" /> <br /><br /> 请注意 JDK 的版本<br /><br />    JUnit 4.1 是基于 Java 5 的升级版本，它使用了 Tiger 中的很多新特性来简化原有的使用方式。正因为如此，它并不能直接运行在 JDK1.4.x 版本上。如果您需要在 JDK1.4.x 版本使用 JUnit 的话，请使用 3.8.1 版本。<br /> <br /> <br />    可以开始编写单元测试了吗？等等……，您打算把单元测试代码放在什么地方呢？把它和被测试代码混在一起，这显然会照成混乱，因为单元测试代码是不会出现在最终产品中的。建议您分别为单元测试代码与被测试代码创建单独的目录，并保证测试代码和被测试代码使用相同的包名。这样既保证了代码的分离，同时还保证了查找的方便。遵照这条原则，我们在项目 coolJUnit 根目录下添加一个新目录 testsrc，并把它加入到项目源代码目录中（加入方式见 图2）。<br /><br />图2 修改项目源代码目录<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure002.jpg" /> <br /><br />    现在我们得到了一条 JUnit 的最佳实践：单元测试代码和被测试代码使用一样的包，不同的目录。<br /><br />    一切准备就绪，一起开始体验如何使用 JUnit 进行单元测试吧。下面的例子来自笔者的开发实践：工具类 WordDealUtil 中的静态方法 wordFormat4DB 是专用于处理 Java 对象名称向数据库表名转换的方法（您可以在代码注释中可以得到更多详细的内容）。下面是第一次编码完成后大致情形：<br /><br /><pre name="code" class="java">package com.ai92.cooljunit;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 对名称、地址等字符串格式的内容进行格式检查
 * 或者格式化的工具类
 * 
 * @author Ai92
 */
public class WordDealUtil {

	/**
	 * 将Java对象名称（每个单词的头字母大写）按照
	 * 数据库命名的习惯进行格式化
	 * 格式化后的数据为小写字母，并且使用下划线分割命名单词
	 * 
	 * 例如：employeeInfo 经过格式化之后变为 employee_info
	 * 
	 * @param name	Java对象名称
	 */
	public static String wordFormat4DB(String name){
		Pattern p = Pattern.compile("[A-Z]");
		Matcher m = p.matcher(name);
		StringBuffer sb = new StringBuffer();
		
		while(m.find()){
			m.appendReplacement(sb, "_"+m.group());
		}
		return m.appendTail(sb).toString().toLowerCase();
	}
}</pre> <br /><br /><br />    它是否能按照预期的效果执行呢？尝试为它编写 JUnit 单元测试代码如下：<br /><br /><pre name="code" class="java">package com.ai92.cooljunit;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class TestWordDealUtil {
	//测试wordFormat4DB正常运行的情况
	@Test public void wordFormat4DBNormal(){
		String target = "employeeInfo";
		String result = WordDealUtil.wordFormat4DB(target);
		
		assertEquals("employee_info", result);
	}
}</pre> <br /><br />    很普通的一个类嘛！测试类 TestWordDealUtil 之所以使用“Test”开头，完全是为了更好的区分测试类与被测试类。测试方法 wordFormat4DBNormal 调用执行被测试方法 WordDealUtil.wordFormat4DB，以判断运行结果是否达到设计预期的效果。需要注意的是，测试方法 wordFormat4DBNormal 需要按照一定的规范书写：<br /><br />    测试方法必须使用注解 org.junit.Test 修饰。 <br />测试方法必须使用 public void 修饰，而且不能带有任何参数。 <br />    测试方法中要处理的字符串为“employeeInfo”，按照设计目的，处理后的结果应该为“employee_info”。assertEquals 是由 JUnit 提供的一系列判断测试结果是否正确的静态断言方法（位于类 org.junit.Assert 中）之一，我们使用它将执行结果 result 和预期值“employee_info”进行比较，来判断测试是否成功。<br /><br />    看看运行结果如何。在测试类上点击右键，在弹出菜单中选择 Run As JUnit Test。运行结果如下图所示：<br /><br /><br />图3 JUnit 运行成功界面<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure003.jpg" /> <br /><br />    绿色的进度条提示我们，测试运行通过了。但现在就宣布代码通过了单元测试还为时过早。记住：您的单元测试代码不是用来证明您是对的，而是为了证明您没有错。因此单元测试的范围要全面，比如对边界值、正常值、错误值得测试；对代码可能出现的问题要全面预测，而这也正是需求分析、详细设计环节中要考虑的。显然，我们的测试才刚刚开始，继续补充一些对特殊情况的测试：<br /><br /><pre name="code" class="java">public class TestWordDealUtil {
	……
	//测试 null 时的处理情况
	@Test public void wordFormat4DBNull(){
		String target = null;
		String result = WordDealUtil.wordFormat4DB(target);
		
		assertNull(result);
	}
	
	//测试空字符串的处理情况
	@Test public void wordFormat4DBEmpty(){
		String target = "";
		String result = WordDealUtil.wordFormat4DB(target);
		
		assertEquals("", result);
	}

	//测试当首字母大写时的情况
	@Test public void wordFormat4DBegin(){
		String target = "EmployeeInfo";
		String result = WordDealUtil.wordFormat4DB(target);
		
		assertEquals("employee_info", result);
	}
	
	//测试当尾字母为大写时的情况
	@Test public void wordFormat4DBEnd(){
		String target = "employeeInfoA";
		String result = WordDealUtil.wordFormat4DB(target);
		
		assertEquals("employee_info_a", result);
	}
	
	//测试多个相连字母大写时的情况
	@Test public void wordFormat4DBTogether(){
		String target = "employeeAInfo";
		String result = WordDealUtil.wordFormat4DB(target);
		
		assertEquals("employee_a_info", result);
	}
}</pre> <br /><br /><br />    再次运行测试。很遗憾，JUnit 运行界面提示我们有两个测试情况未通过测试（图4）——当首字母大写时得到的处理结果与预期的有偏差，造成测试失败（failure）；而当测试对 null 的处理结果时，则直接抛出了异常——测试错误（error）。显然，被测试代码中并没有对首字母大写和 null 这两种特殊情况进行处理，修改如下：<br /><br /><pre name="code" class="java">//修改后的方法wordFormat4DB
/**
	 * 将Java对象名称（每个单词的头字母大写）按照
	 * 数据库命名的习惯进行格式化
	 * 格式化后的数据为小写字母，并且使用下划线分割命名单词
	 * 如果参数name为null，则返回null
	 * 
	 * 例如：employeeInfo 经过格式化之后变为 employee_info
	 * 
	 * @param name Java对象名称
	 */
	public static String wordFormat4DB(String name){
		
		if(name == null){
			return null;
		}
		
		Pattern p = Pattern.compile("[A-Z]");
		Matcher m = p.matcher(name);
		StringBuffer sb = new StringBuffer();
		
		while(m.find()){
			if(m.start() != 0)
				m.appendReplacement(sb, ("_"+m.group()).toLowerCase());
		}
		return m.appendTail(sb).toString().toLowerCase();
	}</pre> <br /><br /><br />图4 JUnit 运行失败界面<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure004.jpg" /> <br /><br />    JUnit 将测试失败的情况分为两种：failure 和 error。Failure 一般由单元测试使用的断言方法判断失败引起，它表示在测试点发现了问题；而 error 则是由代码异常引起，这是测试目的之外的发现，它可能产生于测试代码本身的错误（测试代码也是代码，同样无法保证完全没有缺陷），也可能是被测试代码中的一个隐藏的bug。<br /><br /> 请牢记！<br /><br />    请牢记这一条 JUnit 最佳实践：测试任何可能的错误。单元测试不是用来证明您是对的，而是为了证明您没有错。<br />  <br />    啊哈，再次运行测试，绿条又重现眼前。通过对 WordDealUtil.wordFormat4DB 比较全面的单元测试，现在的代码已经比较稳定，可以作为 API 的一部分提供给其它模块使用了。<br /><br />    不知不觉中我们已经使用 JUnit 漂亮的完成了一次单元测试。可以体会到 JUnit 是多么轻量级，多么简单，根本不需要花心思去研究，这就可以把更多的注意力放在更有意义的事情上——编写完整全面的单元测试。<br /><br />JUnit 深入<br /><br />    当然，JUnit 提供的功能决不仅仅如此简单，在接下来的内容中，我们会看到 JUnit 中很多有用的特性，掌握它们对您灵活的编写单元测试代码非常有帮助。<br /><br />Fixture<br /><br />    何谓 Fixture？它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据，例如测试环境，测试数据等等。在编写单元测试的过程中，您会发现在大部分的测试方法在进行真正的测试之前都需要做大量的铺垫——为设计准备 Fixture 而忙碌。这些铺垫过程占据的代码往往比真正测试的代码多得多，而且这个比率随着测试的复杂程度的增加而递增。当多个测试方法都需要做同样的铺垫时，重复代码的“坏味道”便在测试代码中弥漫开来。这股“坏味道”会弄脏您的代码，还会因为疏忽造成错误，应该使用一些手段来根除它。<br /><br />    JUnit 专门提供了设置公共 Fixture 的方法，同一测试类中的所有测试方法都可以共用它来初始化 Fixture 和注销 Fixture。和编写 JUnit 测试方法一样，公共 Fixture 的设置也很简单，您只需要：<br /><br />使用注解 org,junit.Before 修饰用于初始化 Fixture 的方法。 <br />使用注解 org.junit.After 修饰用于注销 Fixture 的方法。 <br />保证这两种方法都使用 public void 修饰，而且不能带有任何参数。 <br />遵循上面的三条原则，编写出的代码大体是这个样子：<br /><br /><div class="quote_title">引用</div><div class="quote_div">//初始化Fixture方法<br />@Before public void init(){……}<br /><br />//注销Fixture方法<br />@After public void destroy(){……}</div><br /><br />    这样，在每一个测试方法执行之前，JUnit 会保证 init 方法已经提前初始化测试环境，而当此测试方法执行完毕之后，JUnit 又会调用 destroy 方法注销测试环境。注意是每一个测试方法的执行都会触发对公共 Fixture 的设置，也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的（图5）。这样便可以保证各个独立的测试之间互不干扰，以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。<br /><br />图5 方法级别 Fixture 执行示意图<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure005.gif" /> <br /><br />    可是，这种 Fixture 设置方式还是引来了批评，因为它效率低下，特别是在设置 Fixture 非常耗时的情况下（例如设置数据库链接）。而且对于不会发生变化的测试环境或者测试数据来说，是不会影响到测试方法的执行结果的，也就没有必要针对每一个测试方法重新设置一次 Fixture。因此在 JUnit 4 中引入了类级别的 Fixture 设置方法，编写规范如下：<br /><br />    使用注解 org,junit.BeforeClass 修饰用于初始化 Fixture 的方法。 <br />    使用注解 org.junit.AfterClass 修饰用于注销 Fixture 的方法。 <br />    保证这两种方法都使用 public static void 修饰，而且不能带有任何参数。 <br />类级别的 Fixture 仅会在测试类中所有测试方法执行之前执行初始化，并在全部测试方法测试完毕之后执行注销方法（图6）。代码范本如下：<br /><br /><div class="quote_title">引用</div><div class="quote_div">//类级别Fixture初始化方法<br />@BeforeClass public static void dbInit(){……}<br />	<br />//类级别Fixture注销方法<br />	@AfterClass public static void dbClose(){……}</div><br /> <br /><br /><br />图6 类级别 Fixture 执行示意图<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure006.gif" /> <br /><br />异常以及时间测试<br /><br />    注解 org.junit.Test 中有两个非常有用的参数：expected 和 timeout。参数 expected 代表测试方法期望抛出指定的异常，如果运行测试并没有抛出这个异常，则 JUnit 会认为这个测试没有通过。这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。举例来说，方法 supportDBChecker 用于检查用户使用的数据库版本是否在系统的支持的范围之内，如果用户使用了不被支持的数据库版本，则会抛出运行时异常 UnsupportedDBVersionException。测试方法 supportDBChecker 在数据库版本不支持时是否会抛出指定异常的单元测试方法大体如下：<br /><br /><pre name="code" class="java">@Test(expected=UnsupportedDBVersionException.class)
	public void unsupportedDBCheck(){
		……
}</pre><br /> <br />    注解 org.junit.Test 的另一个参数 timeout，指定被测试方法被允许运行的最长时间应该是多少，如果测试方法运行时间超过了指定的毫秒数，则JUnit认为测试失败。这个参数对于性能测试有一定的帮助。例如，如果解析一份自定义的 XML 文档花费了多于 1 秒的时间，就需要重新考虑 XML 结构的设计，那单元测试方法可以这样来写：<br /><br /><pre name="code" class="java">@Test(timeout=1000)
	public void selfXMLReader(){
		……
}</pre><br /> <br /><br /><br />忽略测试方法<br /><br />    JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法，因为有时候由于测试环境受限，并不能保证每一个测试方法都能正确运行。例如下面的代码便表示由于没有了数据库链接，提示 JUnit 忽略测试方法 unsupportedDBCheck：<br /><br /><pre name="code" class="java">@ Ignore(“db is down”)
@Test(expected=UnsupportedDBVersionException.class)
	public void unsupportedDBCheck(){
		……
}</pre><br /> <br /><br /><br />    但是一定要小心。注解 org.junit.Ignore 只能用于暂时的忽略测试，如果需要永远忽略这些测试，一定要确认被测试代码不再需要这些测试方法，以免忽略必要的测试点。<br /><br />测试运行器<br /><br />    又一个新概念出现了——测试运行器，JUnit 中所有的测试方法都是由它负责执行的。JUnit 为单元测试提供了默认的测试运行器，但 JUnit 并没有限制您必须使用默认的运行器。相反，您不仅可以定制自己的运行器（所有的运行器都继承自 org.junit.runner.Runner），而且还可以为每一个测试类指定使用某个具体的运行器。指定方法也很简单，使用注解 org.junit.runner.RunWith 在测试类上显式的声明要使用的运行器即可：<br /><br /><pre name="code" class="java">@RunWith(CustomTestRunner.class)
public class TestWordDealUtil {
……
}</pre><br /> <br /><br /><br />    显而易见，如果测试类没有显式的声明使用哪一个测试运行器，JUnit 会启动默认的测试运行器执行测试类（比如上面提及的单元测试代码）。一般情况下，默认测试运行器可以应对绝大多数的单元测试要求；当使用 JUnit 提供的一些高级特性（例如即将介绍的两个特性）或者针对特殊需求定制 JUnit 测试方式时，显式的声明测试运行器就必不可少了。<br /><br />测试套件<br /><br />    在实际项目中，随着项目进度的开展，单元测试类会越来越多，可是直到现在我们还只会一个一个的单独运行测试类，这在实际项目实践中肯定是不可行的。为了解决这个问题，JUnit 提供了一种批量运行测试类的方法，叫做测试套件。这样，每次需要验证系统功能正确性时，只执行一个或几个测试套件便可以了。测试套件的写法非常简单，您只需要遵循以下规则：<br /><br />创建一个空类作为测试套件的入口。 <br />    使用注解 org.junit.runner.RunWith 和org.junit.runners.Suite.SuiteClasses 修饰这个空类。 <br />将 org.junit.runners.Suite 作为参数传入注解 RunWith，以提示 JUnit 为此类使用套件运行器执行。 <br />    将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。 <br />    保证这个空类使用 public 修饰，而且存在公开的不带有任何参数的构造函数。 <br /><pre name="code" class="java">package com.ai92.cooljunit;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
……

/**
 * 批量测试 工具包 中测试类
 * @author Ai92
 */
@RunWith(Suite.class)
@Suite.SuiteClasses({TestWordDealUtil.class})
public class RunAllUtilTestsSuite {
}</pre> <br /><br /><br />    上例代码中，我们将前文提到的测试类 TestWordDealUtil 放入了测试套件 RunAllUtilTestsSuite 中，在 Eclipse 中运行测试套件，可以看到测试类 TestWordDealUtil 被调用执行了。测试套件中不仅可以包含基本的测试类，而且可以包含其它的测试套件，这样可以很方便的分层管理不同模块的单元测试代码。但是，您一定要保证测试套件之间没有循环包含关系，否则无尽的循环就会出现在您的面前……。<br /><br />参数化测试<br /><br />    回顾一下我们在小节“JUnit 初体验”中举的实例。为了保证单元测试的严谨性，我们模拟了不同类型的字符串来测试方法的处理能力，为此我们编写大量的单元测试方法。可是这些测试方法都是大同小异：代码结构都是相同的，不同的仅仅是测试数据和期望值。有没有更好的方法将测试方法中相同的代码结构提取出来，提高代码的重用度，减少复制粘贴代码的烦恼？在以前的 JUnit 版本上，并没有好的解决方法，而现在您可以使用 JUnit 提供的参数化测试方式应对这个问题。<br /><br />    参数化测试的编写稍微有点麻烦（当然这是相对于 JUnit 中其它特性而言）：<br /><br />    为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。 <br />    为测试类声明几个变量，分别用于存放期望值和测试所用数据。 <br />    为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的，返回值为 java.util.Collection 的公共静态方法，并在此方法中初始化所有需要测试的参数对。 <br />    为测试类声明一个带有参数的公共构造函数，并在其中为第二个环节中声明的几个变量赋值。 <br />   编写测试方法，使用定义的变量作为参数进行测试。 <br />   我们按照这个标准，重新改造一番我们的单元测试代码：<br /><br /><pre name="code" class="java">package com.ai92.cooljunit;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class TestWordDealUtilWithParam {

		private String expected;
	
		private String target;
	
		@Parameters
		public static Collection words(){
	    		return Arrays.asList(new Object[][]{
	          	{"employee_info", "employeeInfo"},		//测试一般的处理情况
	          	{null, null},							//测试 null 时的处理情况
	          	{"", ""},								//测试空字符串时的处理情况
	          	{"employee_info", "EmployeeInfo"},		//测试当首字母大写时的情况
	          	{"employee_info_a", "employeeInfoA"},	//测试当尾字母为大写时的情况
	          	{"employee_a_info", "employeeAInfo"}	//测试多个相连字母大写时的情况
	    		});
		}
	
	 	/**
	 	* 参数化测试必须的构造函数
	 	* @param expected	期望的测试结果，对应参数集中的第一个参数
	 	* @param target	测试数据，对应参数集中的第二个参数
	 	*/
		public TestWordDealUtilWithParam(String expected , String target){
			this.expected = expected;
			this.target = target;
		}
	
	 	/**
	 	* 测试将 Java 对象名称到数据库名称的转换
	 	*/
		@Test public void wordFormat4DB(){
			assertEquals(expected, WordDealUtil.wordFormat4DB(target));
		}
}</pre> <br /><br /><br />    很明显，代码瘦身了。在静态方法 words 中，我们使用二维数组来构建测试所需要的参数列表，其中每个数组中的元素的放置顺序并没有什么要求，只要和构造函数中的顺序保持一致就可以了。现在如果再增加一种测试情况，只需要在静态方法 words 中添加相应的数组即可，不再需要复制粘贴出一个新的方法出来了。<br /><br />JUnit 和 Ant<br /><br />    随着项目的进展，项目的规模在不断的膨胀，为了保证项目的质量，有计划的执行全面的单元测试是非常有必要的。但单靠JUnit提供的测试套件很难胜任这项工作，因为项目中单元测试类的个数在不停的增加，测试套件却无法动态的识别新加入的单元测试类，需要手动修改测试套件，这是一个很容易遗忘得步骤，稍有疏忽就会影响全面单元测试的覆盖率。<br /><br />    当然解决的方法有多种多样，其中将 JUnit 与构建利器 Ant 结合使用可以很简单的解决这个问题。Ant —— 备受赞誉的 Java 构建工具。它凭借出色的易用性、平台无关性以及对项目自动测试和自动部署的支持，成为众多项目构建过程中不可或缺的独立工具，并已经成为事实上的标准。Ant 内置了对 JUnit 的支持，它提供了两个 Task：junit 和 junitreport，分别用于执行 JUnit 单元测试和生成测试结果报告。使用这两个 Task 编写构建脚本，可以很简单的完成每次全面单元测试的任务。 <br /><br />    不过，在使用 Ant 运行 JUnit 之前，您需要稍作一些配置。打开 Eclipse 首选项界面，选择 Ant -> Runtime 首选项（见图7），将 JUnit 4.1 的 JAR 文件添加到 Classpath Tab 页中的 Global Entries 设置项里。记得检查一下 Ant Home Entries 设置项中的 Ant 版本是否在 1.7.0 之上，如果不是请替换为最新版本的 Ant JAR 文件。<br /><br />图7 Ant Runtime 首选项<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure007.jpg" /><br /><br />    剩下的工作就是要编写 Ant 构建脚本 build.xml。虽然这个过程稍嫌繁琐，但这是一件一劳永逸的事情。现在我们就把前面编写的测试用例都放置到 Ant 构建脚本中执行，为项目 coolJUnit 的构建脚本添加一下内容：<br /><br /><pre name="code" class="xml">&lt;?xml version="1.0"?>
&lt;!-- ============================================= 
     auto unittest task    
     ai92                                                                
     ========================================== -->
&lt;project name="auto unittest task" default="junit and report" basedir=".">

		&lt;property name="output folder" value="bin"/>

		&lt;property name="src folder" value="src"/>
	
		&lt;property name="test folder" value="testsrc"/>
	
		&lt;property name="report folder" value="report" />

		&lt;!-- - - - - - - - - - - - - - - - - - 
          target: test report folder init                      
         - - - - - - - - - - - - - - - - - -->
		&lt;target name="test init">
			&lt;mkdir dir="${report folder}"/>
		&lt;/target>
	
		&lt;!-- - - - - - - - - - - - - - - - - - 
          target: compile                      
         - - - - - - - - - - - - - - - - - -->
		&lt;target name="compile">
			&lt;javac srcdir="${src folder}" destdir="${output folder}" />
			&lt;echo>compilation complete!&lt;/echo>
		&lt;/target>

		&lt;!-- - - - - - - - - - - - - - - - - - 
          target: compile test cases                      
         - - - - - - - - - - - - - - - - - -->
		&lt;target name="test compile" depends="test init">
			&lt;javac srcdir="${test folder}" destdir="${output folder}" />
			&lt;echo>test compilation complete!&lt;/echo>
		&lt;/target>
	
		&lt;target name="all compile" depends="compile, test compile">
		&lt;/target>
	
		&lt;!-- ======================================== 
          target: auto test all test case and output report file                      
      	===================================== -->
		&lt;target name="junit and report" depends="all compile">
			&lt;junit printsummary="on" fork="true" showoutput="true">
				&lt;classpath>
					&lt;fileset dir="lib" includes="**/*.jar"/>
					&lt;pathelement path="${output folder}"/>
				&lt;/classpath>
				&lt;formatter type="xml" />
				&lt;batchtest todir="${report folder}">
					&lt;fileset dir="${output folder}">
						&lt;include name="**/Test*.*" />
					&lt;/fileset>
				&lt;/batchtest>
			&lt;/junit>
			&lt;junitreport todir="${report folder}">
				&lt;fileset dir="${report folder}">
					&lt;include name="TEST-*.xml" />
				&lt;/fileset>
				&lt;report format="frames" todir="${report folder}" />
			&lt;/junitreport>
		&lt;/target>
&lt;/project></pre> <br /><br /><br />    Target junit report 是 Ant 构建脚本中的核心内容，其它 target 都是为它的执行提供前期服务。Task junit 会寻找输出目录下所有命名以“Test”开头的 class 文件，并执行它们。紧接着 Task junitreport 会将执行结果生成 HTML 格式的测试报告（图8）放置在“report folder”下。<br /><br />    为整个项目的单元测试类确定一种命名风格。不仅是出于区分类别的考虑，这为 Ant 批量执行单元测试也非常有帮助，比如前面例子中的测试类都已“Test”打头，而测试套件则以“Suite”结尾等等。<br /><br />图8 junitreport 生成的测试报告<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-junit4/figure008.jpg" /> <br /><br />现在执行一次全面的单元测试变得非常简单了，只需要运行一下 Ant 构建脚本，就可以走完所有流程，并能得到一份详尽的测试报告。您可以在 Ant 在线手册 中获得上面提及的每一个 Ant 内置 task 的使用细节。<br /><br />总结<br /><br />    随着越来越多的开发人员开始认同并接受极限编程（XP）的思想，单元测试的作用在软件工程中变得越来越重要。本文旨在将最新的单元测试工具 JUnit 4 介绍给您，以及如何结合 IDE Eclipse 和构建工具 Ant 创建自动化单元测试方案。并且还期望您能够通过本文“感染”一些好的单元测试意识，因为 JUnit 本身仅仅是一份工具而已，它的真正优势来自于它的思想和技术。
          <br/>
          <span style="color:red;">
            <a href="http://wing929.javaeye.com/blog/219065#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 24 Jul 2008 14:44:50 +0800</pubDate>
        <link>http://wing929.javaeye.com/blog/219065</link>
        <guid>http://wing929.javaeye.com/blog/219065</guid>
      </item>
      <item>
        <title>使用 Spring 2.5 TestContext 测试框架</title>
        <author>wing929</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://wing929.javaeye.com">wing929</a>&nbsp;
          链接：<a href="http://wing929.javaeye.com/blog/219062" style="color:red;">http://wing929.javaeye.com/blog/219062</a>&nbsp;
          发表时间: 2008年07月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          Spring 2.5 TestContext 测试框架用于测试基于 Spring 的程序，TestContext 测试框架和低版本 Spring 测试框架没有任何关系，是一个全新的基于注解的测试框架，为 Spring 推荐使用该测试框架。<br />概述<br /><br />   Spring 2.5 相比于 Spring 2.0 所新增的最重要的功能可以归结为以下 3 点：<br /><br />基于注解的 IoC 功能； <br />基于注解驱动的 Spring MVC 功能； <br />基于注解的 TestContext 测试框架。 <br />    Spring 推荐开发者使用新的基于注解的 TestContext 测试框架，本文我们将对此进行详细的讲述。<br /><br />    低版本的 Spring 所提供的 Spring 测试框架构在 JUnit 3.8 基础上扩展而来，它提供了若干个测试基类。而 Spring 2.5 所新增的基于注解的 TestContext 测试框架和低版本的测试框架没有任何关系。它采用全新的注解技术可以让 POJO 成为 Spring 的测试用例，除了拥有旧测试框架所有功能外，TestContext 还添加了一些新的功能，TestContext 可以运行在 JUnit 3.8、JUnit 4.4、TestNG 等测试框架下。<br /><br />    直接使用 JUnit 测试 Spring 程序存在的不足<br /><br />    在拙作《精通 Spring 2.x — 企业应用开发详解》一书中，笔者曾经指出如果直接使用 JUnit 测试基于 Spring 的程序，将存在以下 4 点明显的不足：<br /><br />    导致 Spring 容器多次初始化问题：根据 JUnit 测试用例的调用流程，每执行一个测试方法都会重新创建一个测试用例实例并调用其 setUp() 方法。由于在一般情况下，我们都在 setUp() 方法中初始化 Spring 容器，这意味着测试用例中有多少个测试方法，Spring 容器就会被重复初始化多少次。 <br />    需要使用硬编码方式手工获取 Bean：在测试用例中，我们需要通过 ApplicationContext.getBean() 的方法从 Spirng 容器中获取需要测试的目标 Bean，并且还要进行造型操作。 <br />    数据库现场容易遭受破坏：测试方法可能会对数据库记录进行更改操作，破坏数据库现场。虽然是针对开发数据库进行测试工作的，但如果数据操作的影响是持久的，将会形成积累效应并影响到测试用例的再次执行。举个例子，假设在某个测试方法中往数据库插入一条 ID 为 1 的 t_user 记录，第一次运行不会有问题，第二次运行时，就会因为主键冲突而导致测试用例执行失败。所以测试用例应该既能够完成测试固件业务功能正确性的检查，又能够容易地在测试完成后恢复现场，做到踏雪无迹、雁过无痕。 <br />    不容易在同一事务下访问数据库以检验业务操作的正确性：当测试固件操作数据库时，为了检测数据操作的正确性，需要通过一种方便途径在测试方法相同的事务环境下访问数据库，以检查测试固件数据操作的执行效果。如果直接使用 JUnit 进行测试，我们很难完成这项操作。 <br />    Spring 测试框架是专门为测试基于 Spring 框架应用程序而设计的，它能够让测试用例非常方便地和 Spring 框架结合起来，以上所有问题都将迎刃而解。<br /><br />   一个需要测试的 Spring 服务类<br /><br />    在具体使用 TextContext 测试框架之前，我们先来认识一下需要测试的 UserService 服务类。UserService 服务类中拥有一个处理用户登录的服务方法，其代码如下所示：<br /><br /><pre name="code" class="java">package com.baobaotao.service;

import com.baobaotao.domain.LoginLog;
import com.baobaotao.domain.User;
import com.baobaotao.dao.UserDao;
import com.baobaotao.dao.LoginLogDao;

public class UserService{

    private UserDao userDao;
    private LoginLogDao loginLogDao;

    public void handleUserLogin(User user) {
        user.setCredits( 5 + user.getCredits());
        LoginLog loginLog = new LoginLog();
        loginLog.setUserId(user.getUserId());
        loginLog.setIp(user.getLastIp());
        loginLog.setLoginTime(user.getLastVisit());
        userDao.updateLoginInfo(user);
        loginLogDao.insertLoginLog(loginLog);
    }
    //省略get/setter方法
}</pre><br /><br />    UserService 需要调用 DAO 层的 UserDao 和 LoginLogDao 以及 User 和 LoginLog 这两个 PO 完成业务逻辑，User 和 LoginLog分别对应 t_user 和 t_login_log 这两张数据库表。<br /><br />    在用户登录成功后调用 UserService 中的 handleUserLogin() 方法执行用户登录成功后的业务逻辑：<br /><br />    登录用户添加 5 个积分（t_user.credits）； <br />    登录用户的最后访问时间（t_user.last_visit）和 IP（t_user.last_ip）更新为当前值； <br />    在日志表中（t_login_log）中为用户添加一条登录日志。 <br />    这是一个需要访问数据库并存在数据更改操作的业务方法，它工作在事务环境下。下面是装配该服务类 Bean 的 Spring 配置文件：<br /><br /><pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8" ?>
&lt;beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
<a href="http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" target="_blank">http://www.springframework.org/schema/beans/spring-beans-2.5.xsd</a>
     http://www.springframework.org/schema/tx 
<a href="http://www.springframework.org/schema/tx/spring-tx-2.5.xsd" target="_blank">http://www.springframework.org/schema/tx/spring-tx-2.5.xsd</a>
     http://www.springframework.org/schema/aop 
<a href="http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" target="_blank">http://www.springframework.org/schema/aop/spring-aop-2.5.xsd</a>">
    &lt;!-- 配置数据源  -->
&lt;bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost/sampledb"
        p:username="root"
        p:password="1234"/>
    
    &lt;!-- 配置Jdbc模板  -->
&lt;bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
            p:dataSource-ref="dataSource"/>

    &lt;!-- 配置dao  -->
&lt;bean id="loginLogDao"class="com.baobaotao.dao.LoginLogDao"
           p:jdbcTemplate-ref="jdbcTemplate"/>
&lt;bean id="userDao" class="com.baobaotao.dao.UserDao" 
p:jdbcTemplate-ref="jdbcTemplate"/>

&lt;!-- 事务管理器 -->
&lt;bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
           p:dataSource-ref="dataSource"/>

&lt;bean id="userService" class="com.baobaotao.service.UserService"
             p:userDao-ref="userDao" p:loginLogDao-ref="loginLogDao"/>

    &lt;!-- 使用aop/tx命名空间配置事务管理,这里对service包下的服务类方法提供事务-->
    &lt;aop:config>
&lt;aop:pointcut id="jdbcServiceMethod"
expression= "within(com.baobaotao.service..*)" />
&lt;aop:advisor pointcut-ref="jdbcServiceMethod" advice-ref="jdbcTxAdvice" />
    &lt;/aop:config>
    &lt;tx:advice id="jdbcTxAdvice" transaction-manager="transactionManager">
&lt;tx:attributes>
            &lt;tx:method name="*"/>
        &lt;/tx:attributes>
&lt;/tx:advice>
&lt;/beans></pre><br /><br /><br />   UserService 所关联的 DAO 类和 PO 类都比较简单，请参看本文附件的程序代码。在着手测试 UserSerivce 之前，需要将创建数据库表，你可以在附件的 schema 目录下找到相应的 SQL 脚本文件。<br /><br />   编写 UserService 的测试用例<br /><br />   下面我们为 UserService 编写一个简单的测试用例类，此时的目标是让这个基于 TestContext 测试框架的测试类运行起来，我们将在后面逐步完善这个测试用例。<br /><br /><br />TestUserService.java: 基于注解的测试用例<br />                <br /><pre name="code" class="java">package com.baobaotao.service;

import org.springframework.test.context.junit4.
    AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.Test;
import com.baobaotao.domain.User;

import java.util.Date;

@ContextConfiguration  //①
public class TestUserService extends 
    AbstractTransactionalJUnit4SpringContextTests {
   
@Autowired  //②
   private UserService userService;

   @Test  //③
   public void handleUserLogin(){
       User user = new User();
       user.setUserId(1);
       user.setLastIp("127.0.0.1");
       Date now = new Date();
       user.setLastVisit(now.getTime());
       userService.handleUserLogin(user);
   }
}</pre> <br />    这里，我们让 TestUserService 直接继承于 Spring 所提供的 AbstractTransactionalJUnit4SpringContextTests 的抽象测试类，稍后本文将对这个抽象测试类进行剖析，这里你仅须知道该抽象测试类的作用是让 TestContext 测试框架可以在 JUnit 4.4 测试框架基础上运行起来就可以了。<br /><br />     在 ① 处，标注了一个类级的 @ContextConfiguration 注解，这里 Spring 将按 TestContext 契约查找 classpath:/com/baobaotao/service/TestUserService-context.xml 的 Spring 配置文件，并使用该配置文件启动 Spring 容器。@ContextConfiguration 注解有以下两个常用的属性：<br /><br />locations：可以通过该属性手工指定 Spring 配置文件所在的位置，可以指定一个或多个 Spring 配置文件。如下所示：<br /><br /><div class="quote_title">引用</div><div class="quote_div">@ContextConfiguration(locations={“xx/yy/beans1.xml”,” xx/yy/beans2.xml”}) </div><br /><br />inheritLocations：是否要继承父测试用例类中的 Spring 配置文件，默认为 true。如下面的例子：<br /><br /><pre name="code" class="java">@ContextConfiguration(locations={"base-context.xml"})
 public class BaseTest {
     // ...
 }
 @ContextConfiguration(locations={"extended-context.xml"})
 public class ExtendedTest extends BaseTest {
     // ...
 }</pre><br /> <br /><br /><br />    如果 inheritLocations 设置为 false，则 ExtendedTest 仅会使用 extended-context.xml 配置文件，否则将使用 base-context.xml 和 extended-context.xml 这两个配置文件。<br /><br />    ② 处的 @Autowired 注解让 Spring 容器自动注入 UserService 类型的 Bean。而在 ③ 处标注的 @Test 注解则让 handleUserLogin() 方法成为一个 JUnit 4.4 标准的测试方法， @Test 是 JUnit 4.4 所定义的注解。<br /><br />    在运行 TestUserService 测试类之前，让我们先看一下 TestUserService-context.xml 配置文件的内容：<br /><br /><br />TestUserService 所引用的 Spring 配置文件<br />                <br /><pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8" ?>
&lt;beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
<a href="http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" target="_blank">http://www.springframework.org/schema/beans/spring-beans-2.5.xsd</a>">

&lt;!-- ① 引入清单1定义的Spring配置文件 -->
&lt;import resource="classpath:/applicationContext.xml"/>

&lt;/beans></pre><br /> <br /><br /><br />    在 ① 处引入了清单 1 中定义的 Spring 配置文件，这样我们就可以将其中定义的 UserService Bean 作为测试固件注入到 TestUserService 中了。<br /><br />    在你的 IDE 中（Eclipse、JBuilder、Idea 等），将 JUnit 4.4 类包引入到项目工程中后，在 TestUserService 类中点击右键运行该测试类，将发现 TestUserService 已经可以成功运行了，如 图 1 所示：<br /><br /><br />    图 1. 在 Eclipse 6.0 中运行 TestUserService<br /><img src="http://www.ibm.com/developerworks/cn/java/j-lo-spring25-test/fig001.jpg" /><br /><br />    TestUserService 可以正确运行，说明其 userService 这个测试固件已经享受了 Spring 自动注入的功能。在运行该测试用例后，到数据库中查看 t_user 表和 t_login_lo