All posts by dotte

CURL help

curl     -A  ‘Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko’  -o page1.html      -D cookie1.txt  http://it-ebooks.info/book/6616/

curl -e http://it-ebooks.info/book/6616/ -A  ‘Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko’  -o page2.html -D cookie2.txt -b cookie1.txt  http://filepi.com/i/yOKFHab

get request(urlencode) :

curl -s -k –data-urlencode {message}@C:/requestbody.xml -o C:/responsebody.xml  {mainUrl}

post request(use anyauth):

curl -s  –anyauth -u {username}:{password} –data-urlencode {parm1name}={parm1value} –data-urlencode {parm2name}={parm2value}  -o C:/responsebody.xml  {mainUrl}

post request(file upload) :

curl -s -k -s  –anyauth -u {username}:{password} -A {useragent} -F {parm1name}={parm1value} -F {parm2name}={parm2value} -F file_1=@IMG_0001.JPG -o C:/responsebody.xml  {mainUrl}
Digest认证(use header)
curl -s  –header   Authorization:’Digest username={username}’ –data-urlencode {parm1name}={parm1value} –data-urlencode {parm2name}={parm2value}  -o C:/responsebody.xml  {mainUrl}
curl -s -k -s  –header   Authorization:’Digest username={username}’ -A {useragent} -F {parm1name}={parm1value} -F {parm2name}={parm2value} -F file_1=@IMG_0001.JPG -o C:/responsebody.xml  {mainUrl}

 

NAME

curl – transfer a URL

SYNOPSIS

curl [options] [URL…]

DESCRIPTION

curl is a tool to transfer data from or to a server, using one of the supported protocols (DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET and TFTP). The command is designed to work without user interaction.

curl offers a busload of useful tricks like proxy support, user authentication, FTP upload, HTTP post, SSL connections, cookies, file transfer resume, Metalink, and more. As you will see below, the number of features will make your head spin!

curl is powered by libcurl for all transfer-related features. See libcurl(3) for details.

refer : http://curl.haxx.se/docs/manpage.html

Using curl to automate HTTP jobs

Spring MVC Guide and Resources

 Spring MVC Guide

Step Description
1 Create a Dynamic Web Project with a name HelloWeb and create a package com.tutorialspoint under the src folder in the created project.
2 Drag and drop below mentioned Spring and other libraries into the folder WebContent/WEB-INF/lib.
3 Create a Java class HelloController under the com.tutorialspoint package.
4 Create Spring configuration files web.xml and HelloWeb-servlet.xml under the WebContent/WEB-INF folder.
5 Create a sub-folder with a name jsp under the WebContent/WEB-INF folder. Create a view file hello.jsp under this sub-folder.
6 The final step is to create the content of all the source and configuration files and export the application as explained below.

 

Spring MVC Hello World Example
http://www.tutorialspoint.com/spring/spring_mvc_hello_world_example.htm

 

Download Spring jar package:
http://maven.springframework.org/release/org/springframework/spring/ or http://repo.spring.io/release/org/springframework/spring/

Where can I download Spring Framework jars without using Maven
http://stackoverflow.com/questions/19082860/where-can-i-download-spring-framework-jars-without-using-maven

http://mvnrepository.com

JaCoCo:分析单元测试覆盖率的利器

前言

随着敏捷开发的流行,编写单元测试已经成为业界共识。但如何来衡量单元测试的质量呢?有些管理者片面追求单元测试的数量,导致底下的开发人员投机取巧,编写出大量的重复测试,数量上去了,质量却依然原地踏步。相比单纯追求单元测试的数量,分析单元测试的代码覆盖率是一种更为可行的方式。JaCoCo(Java Code Coverage)就是一种分析单元测试覆盖率的工具,使用它运行单元测试后,可以给出代码中哪些部分被单元测试测到,哪些部分没有没测到,并且给出整个项目的单元测试覆盖情况百分比,看上去一目了然。EclEmma 是基于 JaCoCo 的一个 Eclipse 插件,开发人员可以方便的和其交互。因此,本文先从 EclEmma 入手,给读者一个直观的体验。


使用 EclEmma 在 Eclipse 中查看单元测试覆盖率

EclEmma 是基于 JaCoCo 的 Eclipse 插件,使用它,开发人员可以直观地看到单元测试的覆盖情况。

安装 EclEmma

打开 Eclipse 的软件市场,在其中搜索 EclEmma,找到后完成安装,如下图所示:

图 1. 安装 EclEmma

图 1. 安装 EclEmma

安装完成后,Eclipse 的工具条里会多出下面这样一个图标:

图 2. Coverage 图标

图 2. Coverage 图标

分析单元测试覆盖率

成功安装 EclEmma 后,就可以试着用它来分析项目的单元测试覆盖率了。为了方便演示,我们使用 Eclipse 创建了一个标准 Java 工程。其中包含一个数学工具类,用来计算三个数中的最大值,代码如下:

清单 1. 数学工具类
package com.dw.math;
public class MathUtil {
   public static int max(int a, int b, int c){
       if(a > b){
           if(a > c){
               return a;
           }else{
               return c;
           }
        }else{
           if(b > c){
               return b;
           }else{
               return c;
           }
        }
    }
}

可以看到,这里的算法稍微有点复杂,使用到了多个条件判断分支,因此,特别适合为其编写单元测试。第一个版本的单元测试如下:

清单 2. 第一个版本的单元测试
package com.dw.math;
import static org.junit.Assert.*;
import org.junit.Test;
  public class MathUtilTest {
    @Test
    public void test_max_1_2_3() {
          assertEquals(3, MathUtil.max(1, 2, 3));
    }
}

试着运行一下单元测试覆盖率分析工具:40.0%!似乎不太理想。展开分析报告,双击后在编辑器里可以看到覆盖情况被不同的颜色标识出来,其中绿颜色表示代码被单元测试覆盖到,黄色表示部分覆盖,红色则表示完全没有覆盖到,如下图所示:

图 3. 单元测试覆盖率报告

图 3. 单元测试覆盖率报告

让我们尝试多加一些单元测试,来改善这种情况,请看下面第二个版本的单元测试:

清单 3. 第二个版本的单元测试
package com.dw.math;
import static org.junit.Assert.*;
import org.junit.Test;
  public class MathUtilTest {
  @Test
  public void test_max_1_2_3() {
      assertEquals(3, MathUtil.max(1, 2, 3));
  }
  @Test
  public void test_max_10_20_30() {
      assertEquals(30, MathUtil.max(10, 20, 30));
  }
  @Test
  public void test_max_100_200_300() {
      assertEquals(300, MathUtil.max(100, 200, 300));
  }
}

测试覆盖率还是 40.0%!虽然我们额外再加了两个测试,但覆盖率没有半点提升,这些单元测试其实是重复的,它们在重复测试同一段代码。如果单纯追求单元测试的数量,那么这无疑会给管理者造成错觉,他们觉得单元测试的数量增加了,软件的质量更有保证了;而对于那些喜欢偷懒的程序员,也蒙混过关,但却给软件质量埋下了隐患。让我们删掉这些重复的单元测试,重新思考一下怎么测试这个方法。

首先我们要测试正常情况,这其中又包含 3 种情况:第一个参数最大,第二个参数最大,以及最后一个参数最大。然后我们还需测试几种特殊情况,比如三个参数相同,三个参数中,其中两个相同。让我们照此思路重新编写单元测试:

清单 4. 第三个版本的单元测试
package com.dw.math;
import static org.junit.Assert.*;
import org.junit.Test;
public class MathUtilTest {
  @Test
  public void test_max_1_2_3() {
      assertEquals(3, MathUtil.max(1, 2, 3));
  }
  @Test
  public void test_max_1_3_2() {
      assertEquals(3, MathUtil.max(1, 3, 2));
  }
  @Test
  public void test_max_3_2_1() {
      assertEquals(3, MathUtil.max(3, 2, 1));
  }
  @Test
  public void test_max_0_0_0(){
      assertEquals(0, MathUtil.max(0, 0, 0));
  }
  @Test
  public void test_max_0_1_0(){
      assertEquals(1, MathUtil.max(0, 1, 0));
  }
}

再次运行单元测试分析工具:75.0%!这次比以前有了很大提升,但是结果还不能令人满意,打开分析报告可以看到,有一个分支还是没有覆盖到,如图所示:

图 4. 单元测试覆盖率报告

图 4. 单元测试覆盖率报告

阅读代码可以看出,这种情况是指第一个参数大于第二个参数,却小于第三个参数,因此我们再增加一个单元测试:

清单 5. 再增加一个单元测试
@Test
public void test_max_2_1_3() {
    assertEquals(3, MathUtil.max(2, 1, 3));
}

再运行一遍单元测试分析工具:100.0%!终于我们的单元测试达到了全覆盖,这样我们对自己开发的代码更有信心了。当然,我们在这里并不是为了单纯的追求这个数字,在增加单元测试覆盖率的诱导下,我们重新理清了测试的步骤,写出了更有意义、更全面的单元测试。而且根据单元测试分析工具给的反馈,我们还发现了先前没有想到的情形。因此,单元测试的覆盖率并不只是一个为了取悦管理者的数据,它实实在在地帮助我们改善了代码的质量,增加了我们对所编写代码的信心。


给管理者的单元测试覆盖率报告

管理者天生喜欢阅读报告。他们不会屈尊坐在你的办公桌前,让你向他展示 Eclipse 中这一片花花绿绿的东西。而且这份报告对他们至关重要,他们需要用它向上级汇报;年底回顾时,他们也可以兴奋地宣称产品的单元测试覆盖率增加了多少。作为一名开发人员,我们很大一部分工作量就在于满足管理者的这种需求。因此,本节我们讨论如何将 JaCoCo 集成到 Ant 脚本中,生成漂亮的单元测试覆盖率报告。

准备工作

在集成 JaCoCo 前,必须先确保你的 Java 工程有一个可执行的 Ant 构建脚本。一个简单的 Ant 构建脚本一般会执行如下任务:编译(包括编译工程代码和测试代码)、打包和执行单元测试。下面是本文示例 Java 项目所用的 Ant 构建脚本,读者可结合自己的项目及文件路径,在此基础之上进行修改。

清单 6. build.xml
<project name="math" basedir="." default="junit">
 <!--预定义的属性和 classpath -->
 <property name="src.dir" value="src" />
 <property name="test.dir" value="test" />
 <property name="build.dir" value="build" />
 <property name="classes.dir" value="${build.dir}/classes" />
 <property name="tests.dir" value="${build.dir}/tests" />
 <property name="jar.dir" value="${build.dir}/jar" />
 <property name="lib.dir" value="lib" />
 
 <path id="classpath">
     <fileset dir="${lib.dir}" includes="**/*.jar" />
 </path>
 
  <!--清除上次构建 -->
  <target name="clean">
      <delete dir="${build.dir}" />
  </target>
  
  <!--编译代码,包括单元测试 -->
  <target name="compile" depends="clean">
     <mkdir dir="${classes.dir}" />
     <mkdir dir="${tests.dir}" />
     <javac srcdir="${src.dir}" destdir="${classes.dir}" />
     <javac srcdir="${test.dir}" destdir="${tests.dir}">
     <classpath>
     <path refid="classpath" />
     <path location="${classes.dir}" />
     </classpath>
     </javac>
  </target>
  
  <!--打包 -->
  <target name="jar" depends="compile">
     <mkdir dir="${jar.dir}" />
     <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
     </jar>
  </target>
  
  <!--运行单元测试 -->
  <target name="junit" depends="jar">
     <junit printsummary="yes">
       <classpath>
       <path refid="classpath"/>
       <path location="${classes.dir}" />
       <path location="${tests.dir}" />
       </classpath>
       <batchtest fork="yes">
       <fileset dir="${test.dir}" includes="**/*Test.java"/>
       </batchtest>
     </junit>
  </target>
</project>

集成 JaCoCo

首先需要从 然后就是使用 JaCoCo 官网下载 需要的版本,然后将下载得到的压缩文件解压,将其中的 jacocoant.jar 拷贝至 Java 工程下存放第三方 jar 包的目录,在示例工程里,我有一个和 src 平级的 lib 目录,jacocoant.jar 就放到了这个目录底下,读者可根据自己的项目组织结构做相应调整。然后我们需要在 Ant 构建脚本中定义新的任务:

清单 7. 定义新的构建任务
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath refid="classpath" />
</taskdef>

现在就可以在 Ant 构建脚本中使用 JaCoCo 了。需要注意的是,为了避免命名冲突,需要给 Ant 构建脚本加入新的 XML 命名空间:

清单 8. 加入新的 JaCoCo 命名空间
<project name="math" basedir="." xmlns:jacoco="antlib:org.jacoco.ant" default="junit">

我们主要使用 JaCoCo 的两个任务:首先是jacoco:coverage,用来生成单元测试覆盖率数据,这是一个二进制文件,为了生成从该文件生成报表,我们还要调用另外一个任务jacoco:report,它的输入为jacoco:coverage生成的二进制文件,输出报表。报表有多种格式可选,可以是 HTML、XML、CSV 等。具体的脚本如下:

清单 9. 使用 JaCoCo 生成测试覆盖率和报表
<jacoco:coverage destfile="${build.dir}/jacoco.exec">
  <junit fork="true" forkmode="once" printsummary="yes">
     <classpath>
         <path refid="classpath" />
         <path location="${classes.dir}" />
         <path location="${tests.dir}" />
     </classpath>
     <batchtest fork="yes">
          <fileset dir="${test.dir}" includes="**/*Test.java"/>
     </batchtest>
  </junit>
 </jacoco:coverage>
 
 <jacoco:report>
   <executiondata>
        <file file="${build.dir}/jacoco.exec"/>
   </executiondata>
   <structure name="dw demo">
      <classfiles>
           <fileset dir="${classes.dir}"/>
      </classfiles>
      <sourcefiles encoding="UTF-8">
           <fileset dir="${src.dir}"/>
      </sourcefiles>
   </structure>
  <html destdir="${build.dir}"/>
</jacoco:report>

JaCoCo 的任务定义非常清晰,在这里略作说明。首先需要将原来的junit任务嵌入jacoco:coverage,而且需要指定fork="true",代表单元测试需要另起一个 JVM 执行,否则 JaCoCo 就会执行失败。destfile="${build.dir}/jacoco.exec"指定生成的测试覆盖率文件输出到什么地方,后面生成报告的时候需要输入该文件的地址。然后就是使用 jacoco:report 生成报告,指定前面任务生成的单元测试覆盖率文件、编译好的类文件以及源代码,最后选择一种格式,这里使用 html,生成报告。打开报告的存放路径,就可以看到如下所示的单元测试覆盖率报告:

图 5. HTML 版的单元测试覆盖率报告

图 5. HTML 版的单元测试覆盖率报告


和同类产品比较

市面上流行的单元测试覆盖率工具还有 CloverCobertura。和它们相比,JaCoCo 有如下优势:

  1. JaCoCo 拥有友好的授权形式。JaCoCo 使用了 Eclipse Public License,方便个人用户和商业用户使用。而 Clover 对于商业用户是收费的。
  2. JaCoCo 被良好地集成进各种工具中。在 Java 社区里,很多流行的工具都可以集成 JaCoCo,比如 SonarQube、Jenkins、Netbeans、Eclipse、IntelliJ IDEA、Gradle 等。
  3. JaCoCo 社区非常活跃,它是目前唯一支持 Java 8 的单元测试覆盖率工具。而且关于 JaCoCo 的文档相对较多,降低了学习门槛。

结束语

本文为大家介绍了如何使用 JaCoCo 分析项目的单元测试覆盖率,文章先从 JaCoCo 的 Eclipse 插件 EclEmma 开始,直观地介绍了如何一步步提高单元测试质量,最终达到对代码的全覆盖;然后为大家介绍了如何将 JaCoCo 集成到 Ant 构建脚本中,生成漂亮的单元测试覆盖率报告。但是使用 JaCoCo 只是第一步,重要的是开发人员能够根据工具所给的反馈,不断改进自己的单元测试,写出高质量的代码。

参考资料

学习

from:http://www.ibm.com/developerworks/cn/java/j-lo-jacoco/index.html

 

Spring MVC 3 深入总结

一、前言:

大家好,Spring3 MVC是非常优秀的MVC框架,由其是在3.0版本号公布后,如今有越来越多的团队选择了Spring3 MVC了。Spring3 MVC结构简单,应了那句话简单就是美,并且他强大不失灵活,性能也非常优秀。

官方的下载网址是:http://www.springsource.org/download   (本文使用是的Spring 3.0.5版本号)

 

Struts2也是比較优秀的MVC构架,长处非常多比方良好的结构。但这里想说的是缺点,Struts2因为採用了值栈、OGNL表达式、struts2标签库等,会导致应用的性能下降。Struts2的多层拦截器、多实例action性能都非常好。能够參考我写的一篇关于Spring MVC与Struts2与Servlet比較的文章 http://elf8848.iteye.com/admin/blogs/698217

 

Spring3 MVC的长处:

1、Spring3 MVC的学习难度小于Struts2,Struts2用不上的多余功能太多。呵呵,当然这不是决定因素。

2、Spring3 MVC非常easy就能够写出性能优秀的程序,Struts2要处处小心才干够写出性能优秀的程序(指MVC部分)

3、Spring3 MVC的灵活是你无法想像的,Spring的扩展性有口皆碑,Spring3 MVC当然也不会落后,不会因使用了MVC框架而感到有不论什么的限制。

 

Struts2的众多长处:略…   (呵呵,是不是不公平?)

 

众多文章开篇时总要吹些牛,吸引一下读者的眼球,把读者的胃口调起来,这样大家才有兴趣接着往后看。本文也没能例外。只是保证你看了之后不会懊悔定有收获。

 

 

二、核心类与接口:

 

先来了解一下,几个重要的接口与类。如今不知道他们是干什么的没关系,先混个脸熟,为以后认识他们打个基础。

 

DispatcherServlet   — 前置控制器

 

HandlerMapping接口 — 处理请求的映射

HandlerMapping接口的实现类:

SimpleUrlHandlerMapping  通过配置文件,把一个URL映射到Controller

DefaultAnnotationHandlerMapping  通过注解,把一个URL映射到Controller类上

 

HandlerAdapter接口 — 处理请求的映射

AnnotationMethodHandlerAdapter类,通过注解,把一个URL映射到Controller类的方法上

 

Controller接口 — 控制器

因为我们使用了@Controller注解,加入了@Controller注解注解的类就能够担任控制器(Action)的职责,

所以我们并没实用到这个接口。

 

 

 

HandlerInterceptor 接口–拦截器

无图,我们自己实现这个接口,来完毕拦截的器的工作。

 

 

ViewResolver接口的实现类

UrlBasedViewResolver类 通过配置文件,把一个视图名交给到一个View来处理

InternalResourceViewResolver类,比上面的类,增加了JSTL的支持

 

View接口

JstlView类

 

LocalResolver接口

 

HandlerExceptionResolver接口 –异常处理

SimpleMappingExceptionResolver实现类

 

 

ModelAndView类

无图。

 

 

 

 

 

三、核心流程图

 

本图是我个人画的,有不严谨的地方,大家对付看吧。总比没的看强。

 

 
四、DispatcherServlet说明

 

使用Spring MVC,配置DispatcherServlet是第一步。

DispatcherServlet是一个Servlet,所以能够配置多个DispatcherServlet。

DispatcherServlet是前置控制器,配置在web.xml文件里的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,根据某某规则分发到目标Controller(我们写的Action)来处理。

 

“某某规则”:是依据你使用了哪个HandlerMapping接口的实现类的不同而不同。

 

先来看第一个样例:

Xml代码
  1. <web-app>
  2.     <servlet>
  3.         <servlet-name>example</servlet-name>
  4.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  5.         <load-on-startup>1</load-on-startup>
  6.     </servlet>
  7.     <servlet-mapping>
  8.         <servlet-name>example</servlet-name>
  9.         <url-pattern>*.form</url-pattern>
  10.     </servlet-mapping>
  11. </web-app>

<load-on-startup>1</load-on-startup>是启动顺序,让这个Servlet随Servletp容器一起启动。

<url-pattern>*.form</url-pattern> 会拦截*.form结尾的请求。

 

<servlet-name>example</servlet-name>这个Servlet的名字是example,能够有多个DispatcherServlet,是通过名字来区分的。每个DispatcherServlet有自己的WebApplicationContext上下文对象。同一时候保存的ServletContext中和Request对象中,关于key,以后说明。

 

在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF目录下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件里定义的bean。

 

 

第二个样例:

Xml代码
  1. <servlet>
  2.     <servlet-name>springMVC</servlet-name>
  3.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4.     <init-param>
  5.         <param-name>contextConfigLocation</param-name>
  6.         <param-value>classpath*:/springMVC.xml</param-value>
  7.     </init-param>
  8.     <load-on-startup>1</load-on-startup>
  9. </servlet>
  10. <servlet-mapping>
  11.     <servlet-name>springMVC</servlet-name>
  12.     <url-pattern>/</url-pattern>
  13. </servlet-mapping>

指明了配置文件的文件名称,不使用默认配置文件名称,而使用springMVC.xml配置文件。

当中<param-value>**.xml</param-value> 这里能够使用多种写法
1、不写,使用默认值:/WEB-INF/<servlet-name>-servlet.xml
2、<param-value>/WEB-INF/classes/springMVC.xml</param-value>
3、<param-value>classpath*:springMVC-mvc.xml</param-value>
4、多个值用逗号分隔
Servlet拦截匹配规则能够自已定义,Servlet拦截哪种URL合适?

当映射为@RequestMapping(“/user/add”)时:
1、拦截*.do,比如:/user/add.do,弊端:全部的url都要以.do结尾。不会影响訪问静态文件。
2、拦截/app/*,比如:/app/user/add,弊端:请求的url都要包括/app,@RequestMapping(“/user/add”)中不需要包括/app。
3、拦截/,比如:/user/add,弊端:对jpg,js,css静态文件的訪问也被拦截不能正常显示。后面有解决的方法。
4、拦截/*,能够走到Action中,但转发到jsp时再次被拦截,不能訪问到jsp。

 

 

五、双亲上下文的说明

 

假设你使用了listener监听器来载入配置,一般在Struts+Spring+Hibernate的项目中都是使用listener监听器的。例如以下

Java代码
  1. <listener>
  2.   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  3. </listener>

Spring会创建一个全局的WebApplicationContext上下文,称为根上下文 ,保存在 ServletContext中,key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性的值。能够使用工具类取出上下文:WebApplicationContextUtils.getWebApplicationContext(ServletContext);

 

DispatcherServlet是一个Servlet,能够同一时候配置多个,每一个 DispatcherServlet有一个自己的 WebApplicationContext上下文,这个上下文继承了 根上下文 中全部东西。 保存在 ServletContext中,key是”org.springframework.web.servlet.FrameworkServlet.CONTEXT”+Servlet名称。当一个Request对象产生时,会把这个WebApplicationContext上下文保存在Request对象中,key是DispatcherServlet.class.getName() + “.CONTEXT”。能够使用工具类取出上下文:RequestContextUtils.getWebApplicationContext(request);

 

Spring中的 ApplicationContext实例能够被限制在不同的作用域(scope)中。
在web MVC框架中,每一个 DispatcherServlet有它自己的WebApplicationContext ,这个context继承了根 WebApplicationContext 的全部bean定义。
这些继承的bean也能够在每一个serlvet自己的所属的域中被覆盖(override),覆盖后的bean 能够被设置上仅仅有这个servlet实例自己使用的属性。

 

总结:不使用listener监听器来载入spring的配置,改用DispatcherServlet来载入spring的配置,不要双亲上下文,仅仅使用一个DispatcherServlet,事情就简单了,什么麻烦事儿也没有了。

 

 

六、springMVC-mvc.xml 配置文件片段解说 (未使用默认配置文件名称)

 

Xml代码
  1.    <!– 自己主动扫描的包名 –>
  2.    <context:component-scan base-package=”com.app,com.core,JUnit4″ ></context:component-scan>
  3.    <!– 默认的注解映射的支持 –>
  4.    <mvc:annotation-driven />
  5.    <!– 视图解释类 –>
  6.    <bean class=”org.springframework.web.servlet.view.InternalResourceViewResolver”>
  7.     <property name=”prefix” value=”/WEB-INF/jsp/”/>
  8.     <property name=”suffix” value=”.jsp”/><!–可为空,方便实现自已的根据扩展名来选择视图解释类的逻辑  –>
  9.     <property name=”viewClass” value=”org.springframework.web.servlet.view.JstlView” />
  10.    </bean>
  11. <!– 拦截器 –>
  12.    <mvc:interceptors>
  13.     <bean class=”com.core.mvc.MyInteceptor” />
  14. </mvc:interceptors>
  15.     <!– 对静态资源文件的訪问  方案一 (二选一) –>
  16.     <mvc:default-servlet-handler/>
  17.     <!– 对静态资源文件的訪问  方案二 (二选一)–>
  18. <mvc:resources mapping=”/images/**” location=”/images/” cache-period=”31556926″/>
  19. <mvc:resources mapping=”/js/**” location=”/js/” cache-period=”31556926″/>
  20. <mvc:resources mapping=”/css/**” location=”/css/” cache-period=”31556926″/>

 

<context:component-scan/> 扫描指定的包中的类上的注解,经常使用的注解有:

@Controller 声明Action组件
@Service    声明Service组件    @Service(“myMovieLister”)
@Repository 声明Dao组件
@Component   泛指组件, 当不好归类时.
@RequestMapping(“/menu”)  请求映射
@Resource  用于注入,( j2ee提供的 ) 默认按名称装配,@Resource(name=”beanName”)
@Autowired 用于注入,(srping提供的) 默认按类型装配
@Transactional( rollbackFor={Exception.class}) 事务管理
@ResponseBody
@Scope(“prototype”)   设定bean的作用域

 

<mvc:annotation-driven /> 是一种简写形式,全然能够手动配置替代这样的简写形式,简写形式能够让初学都高速应用默认配置方案。<mvc:annotation-driven /> 会自己主动注冊DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,是spring MVC为@Controllers分发请求所必须的。
并提供了:数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB),读写JSON的支持(Jackson)。
后面,我们处理响应ajax请求时,就使用到了对json的支持。
后面,对action写JUnit单元測试时,要从spring IOC容器中取DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,来完毕測试,取的时候要知道是<mvc:annotation-driven />这一句注冊的这两个bean。

 

<mvc:interceptors/> 是一种简写形式。通过看前面的大图,知道,我们能够配置多个HandlerMapping。<mvc:interceptors/>会为每个HandlerMapping,注入一个拦截器。事实上我们也能够手动配置为每个HandlerMapping注入一个拦截器。

 

<mvc:default-servlet-handler/> 使用默认的Servlet来响应静态文件。

 

<mvc:resources mapping=”/images/**” location=”/images/” cache-period=”31556926″/> 匹配URL  /images/**  的URL被当做静态资源,由Spring读出到内存中再响应http。
七、怎样訪问到静态的文件,如jpg,js,css?

怎样你的DispatcherServlet拦截 *.do这种URL,就不存在訪问不到静态资源的问题。假设你的DispatcherServlet拦截“/”,拦截了全部的请求,同一时候对*.js,*.jpg的訪问也就被拦截了。

 

目的:能够正常訪问静态文件,不要找不到静态文件报404。

方案一:激活Tomcat的defaultServlet来处理静态文件

Xml代码
  1. <servlet-mapping>
  2.     <servlet-name>default</servlet-name>
  3.     <url-pattern>*.jpg</url-pattern>
  4. </servlet-mapping>
  5. <servlet-mapping>
  6.     <servlet-name>default</servlet-name>
  7.     <url-pattern>*.js</url-pattern>
  8. </servlet-mapping>
  9. <servlet-mapping>
  10.     <servlet-name>default</servlet-name>
  11.     <url-pattern>*.css</url-pattern>
  12. </servlet-mapping>
  13. 要配置多个,每种文件配置一个

要写在DispatcherServlet的前面, 让 defaultServlet先拦截,这个就不会进入Spring了,我想性能是最好的吧。

Tomcat, Jetty, JBoss, and GlassFish  默认 Servlet的名字 — “default”
Google App Engine 默认 Servlet的名字 — “_ah_default”
Resin 默认 Servlet的名字 — “resin-file”
WebLogic 默认 Servlet的名字  — “FileServlet”
WebSphere  默认 Servlet的名字 — “SimpleFileServlet”

 
方案二: 在spring3.0.4以后版本号提供了mvc:resources
mvc:resources 的用法:

Xml代码
  1. <!– 对静态资源文件的訪问 –>
  2. <mvc:resources mapping=”/images/**” location=”/images/” />

/images/**映射到ResourceHttpRequestHandler进行处理,location指定静态资源的位置.能够是web application根文件夹下、jar包里面,这样能够把静态资源压缩到jar包中。cache-period 能够使得静态资源进行web cache

假设出现以下的错误,可能是没有配置<mvc:annotation-driven />的原因。
报错WARNING: No mapping found for HTTP request with URI [/mvc/user/findUser/lisi/770] in DispatcherServlet with name ‘springMVC’

 

使用<mvc:resources/>元素,把mapping的URI注冊到SimpleUrlHandlerMapping的urlMap中,
key为mapping的URI pattern值,而value为ResourceHttpRequestHandler,
这样就巧妙的把对静态资源的訪问由HandlerMapping转到ResourceHttpRequestHandler处理并返回,所以就支持classpath文件夹,jar包内静态资源的訪问.
另外须要注意的一点是,不要对SimpleUrlHandlerMapping设置defaultHandler.由于对static uri的defaultHandler就是ResourceHttpRequestHandler,
否则无法处理static resources request.

 

 

方案三 ,使用<mvc:default-servlet-handler/>

 

Xml代码
  1. <mvc:default-servlet-handler/>

 

会把”/**” url,注冊到SimpleUrlHandlerMapping的urlMap中,把对静态资源的訪问由HandlerMapping转到org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler处理并返回.
DefaultServletHttpRequestHandler使用就是各个Servlet容器自己的默认Servlet.

 

 

补充说明:多个HandlerMapping的运行顺序问题:

DefaultAnnotationHandlerMapping的order属性值是:0

< mvc:resources/ >自己主动注冊的 SimpleUrlHandlerMapping的order属性值是: 2147483646

 

<mvc:default-servlet-handler/>自己主动注冊 的SimpleUrlHandlerMapping 的order属性值是: 2147483647

 

spring会先运行order值比較小的。当訪问一个a.jpg图片文件时,先通过 DefaultAnnotationHandlerMapping 来找处理器,一定是找不到的,我们没有叫a.jpg的Action。再 按order值升序找,因为最后一个 SimpleUrlHandlerMapping 是匹配 “/**”的,所以一定会匹配上,再响应图片。

 

訪问一个图片,还要走层层匹配。真不知性能怎样?改天做一下压力測试,与Apache比一比。

 

最后再说明一下,怎样你的DispatcherServlet拦截 *.do这种URL,就不存上述问题了。

 
八、请求怎样映射到详细的Action中的方法?
方案一:基于xml配置映射,能够利用SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping进行Url映射和拦截请求。
配置方法略。

方案二:基于注解映射,能够使用DefaultAnnotationHandlerMapping。

Xml代码
  1. <bean class=”org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping”>  </bean>

 

但前面我们配置了<mvc:annotation-driven />,他会自己主动注冊这个bean,就不需要我们显示的注冊这个bean了。
以上都能够注入interceptors,实现权限控制等前置工作。
我们使用第2种,基于注解来使用spring MVC

 

 

并在action类上使用:
@Controller
@RequestMapping(“/user”)

九、Spring中的拦截器:
Spring为我们提供了:
org.springframework.web.servlet.HandlerInterceptor接口,

org.springframework.web.servlet.handler.HandlerInterceptorAdapter适配器,
实现这个接口或继承此类,能够很方便的实现自己的拦截器。

有下面三个方法:

Action之前运行:
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler);

生成视图之前运行
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView);

最后运行,可用于释放资源
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)

分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在preHandle中,能够进行编码、安全控制等处理;
在postHandle中,有机会改动ModelAndView;
在afterCompletion中,能够依据ex是否为null推断是否发生了异常,进行日志记录。
參数中的Object handler是下一个拦截器。

十、怎样使用拦截器?
自己定义一个拦截器,要实现HandlerInterceptor接口:

Java代码
  1. public class MyInteceptor implements HandlerInterceptor {
  2.     略。。。
  3. }

 

Spring MVC并没有总的拦截器,不能对全部的请求进行前后拦截。
Spring MVC的拦截器,是属于HandlerMapping级别的,能够有多个HandlerMapping ,每一个HandlerMapping能够有自己的拦截器。
当一个请求按Order值从小到大,顺序运行HandlerMapping接口的实现类时,哪一个先有返回,那就能够结束了,后面的HandlerMapping就不走了,本道工序就完毕了。就转到下一道工序了。
拦截器会在什么时候运行呢? 一个请求交给一个HandlerMapping时,这个HandlerMapping先找有没有处理器来处理这个请求,怎样找到了,就运行拦截器,运行完拦截后,交给目标处理器。
假设没有找到处理器,那么这个拦截器就不会被运行。
在spring MVC的配置文件里配置有三种方法:
方案一,(近似)总拦截器,拦截全部url

Java代码
  1.    <mvc:interceptors>
  2.     <bean class=”com.app.mvc.MyInteceptor” />
  3. </mvc:interceptors>

为什么叫“近似”,前面说了,Spring没有总的拦截器。

<mvc:interceptors/>会为每一 个HandlerMapping,注入一个拦截器。总有一个HandlerMapping是能够找到处理器的,最多也仅仅找到一个处理器,所以这个拦截器总会被运行的。起到了总拦截器的作用。
方案二, (近似) 总拦截器, 拦截匹配的URL。

Xml代码
  1. <mvc:interceptors >
  2.   <mvc:interceptor>
  3.         <mvc:mapping path=”/user/*” /> <!– /user/*  –>
  4.         <bean class=”com.mvc.MyInteceptor”></bean>
  5.     </mvc:interceptor>
  6. </mvc:interceptors>

就是比 方案一多了一个URL匹配。

 

 

 

方案三,HandlerMappint上的拦截器

Xml代码
  1. <bean class=”org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping”>
  2.  <property name=”interceptors”>
  3.      <list>
  4.          <bean class=”com.mvc.MyInteceptor”></bean>
  5.      </list>
  6.  </property>
  7. </bean>

假设使用了<mvc:annotation-driven />, 它会自己主动注冊DefaultAnnotationHandlerMapping 与AnnotationMethodHandlerAdapter 这两个bean,所以就没有机会再给它注入interceptors属性,就无法指定拦截器。

当然我们能够通过人工配置上面的两个Bean,不使用 <mvc:annotation-driven />,就能够 给interceptors属性 注入拦截器了。

 

事实上我也不建议使用<mvc:annotation-driven />,而建议手动写配置文件,来替代 <mvc:annotation-driven />,这就控制力就强了。

 

 

 

 

十一、怎样实现全局的异常处理?

在spring MVC的配置文件里:

Xml代码
  1. <!– 总错误处理–>
  2. <bean id=”exceptionResolver” class=”org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”>
  3.     <property name=”defaultErrorView”>
  4.         <value>/error/error</value>
  5.     </property>
  6.     <property name=”defaultStatusCode”>
  7.         <value>500</value>
  8.     </property>
  9. <property name=”warnLogCategory”>
  10.         <value>org.springframework.web.servlet.handler.SimpleMappingExceptionResolver</value>
  11.     </property>
  12. </bean>

 

这里基本的类是SimpleMappingExceptionResolver类,和他的父类AbstractHandlerExceptionResolver类。

详细能够配置哪些属性,我是通过查看源代码知道的。

你也能够实现HandlerExceptionResolver接口,写一个自己的异常处理程序。spring的扩展性是非常好的。

 

 

通过SimpleMappingExceptionResolver我们能够将不同的异常映射到不同的jsp页面(通过exceptionMappings属性的配置)。

 

同一时候我们也能够为全部的异常指定一个默认的异常提示页面(通过defaultErrorView属性的配置),假设所抛出的异常在exceptionMappings中没有相应的映射,则Spring将用此默认配置显示异常信息。

注意这里配置的异常显示界面均仅包含主文件名称,至于文件路径和后缀已经在viewResolver中指定。如/error/error表示/error/error.jsp

 

 

显示错误的jsp页面:

Html代码
  1. <%@ page language=”java” contentType=”text/html; charset=GBK”
  2.     pageEncoding=”GBK”%>
  3. <%@ page import=”java.lang.Exception”%>
  4. <!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN” “http://www.w3.org/TR/html4/loose.dtd”>
  5. <html>
  6. <head>
  7. <meta http-equiv=”Content-Type” content=”text/html; charset=GBK”>
  8. <title>错误页面</title>
  9. </head>
  10. <body>
  11. <h1>出错了</h1>
  12. <%
  13. Exception e = (Exception)request.getAttribute(“exception”);
  14. out.print(e.getMessage());
  15. %>
  16. </body>
  17. </html>

当中一句:request.getAttribute(“exception”),key是exception,也是在SimpleMappingExceptionResolver类默认指定的,是可能通过配置文件改动这个值的,大家能够去看源代码。

 

參考文章:

http://www.blogjava.net/wuxufeng8080/articles/191150.html

http://fangjunai.blog.163.com/blog/static/1124970520108102013839/

 

 

 

十二、怎样把全局异常记录到日志中?

在前的配置中,当中有一个属性warnLogCategory,值是“SimpleMappingExceptionResolver类的全限定名”。我是在SimpleMappingExceptionResolver类父类AbstractHandlerExceptionResolver类中找到这个属性的。查看源代码后得知:假设warnLogCategory不为空,spring就会使用apache的org.apache.commons.logging.Log日志工具,记录这个异常,级别是warn。

值:“org.springframework.web.servlet.handler.SimpleMappingExceptionResolver”,是“SimpleMappingExceptionResolver类的全限定名”。这个值不是随便写的。  由于我在log4j的配置文件里还要增加log4j.logger.org.springframework.web.servlet.handler.SimpleMappingExceptionResolver=WARN,保证这个级别是warn的日志一定会被记录,即使log4j的根日志级别是ERROR。

 

 

 

 

十三、怎样给spring3 MVC中的Action做JUnit单元測试?

使用了spring3 MVC后,给action做单元測试也非常方便,我曾经从来不给action写单元測试的,再在不同了,方便了,所以一定要写。

 

JUnitActionBase类是全部JUnit的測试类的父类

 

Java代码
  1. package test;
  2. import javax.servlet.http.HttpServletRequest;
  3. import javax.servlet.http.HttpServletResponse;
  4. import org.junit.BeforeClass;
  5. import org.springframework.mock.web.MockServletContext;
  6. import org.springframework.web.context.WebApplicationContext;
  7. import org.springframework.web.context.support.XmlWebApplicationContext;
  8. import org.springframework.web.servlet.HandlerAdapter;
  9. import org.springframework.web.servlet.HandlerExecutionChain;
  10. import org.springframework.web.servlet.HandlerMapping;
  11. import org.springframework.web.servlet.ModelAndView;
  12. import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
  13. import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
  14. /**
  15. * 说明: JUnit測试action时使用的基类
  16. *
  17. * @author  赵磊
  18. * @version 创建时间:2011-2-2 下午10:27:03
  19. */
  20. public class JUnitActionBase {
  21.     private static HandlerMapping handlerMapping;
  22.     private static HandlerAdapter handlerAdapter;
  23.     /**
  24.      * 读取spring3 MVC配置文件
  25.      */
  26.     @BeforeClass
  27.  public static void setUp() {
  28.         if (handlerMapping == null) {
  29.             String[] configs = { “file:src/springConfig/springMVC.xml” };
  30.             XmlWebApplicationContext context = new XmlWebApplicationContext();
  31.             context.setConfigLocations(configs);
  32.             MockServletContext msc = new MockServletContext();
  33.             context.setServletContext(msc);         context.refresh();
  34.             msc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
  35.             handlerMapping = (HandlerMapping) context
  36.                     .getBean(DefaultAnnotationHandlerMapping.class);
  37.             handlerAdapter = (HandlerAdapter) context.getBean(context.getBeanNamesForType(AnnotationMethodHandlerAdapter.class)[0]);
  38.         }
  39.     }
  40.     /**
  41.      * 运行request对象请求的action
  42.      *
  43.      * @param request
  44.      * @param response
  45.      * @return
  46.      * @throws Exception
  47.      */
  48.     public ModelAndView excuteAction(HttpServletRequest request, HttpServletResponse response)
  49.  throws Exception {
  50.         HandlerExecutionChain chain = handlerMapping.getHandler(request);
  51.         final ModelAndView model = handlerAdapter.handle(request, response,
  52.                 chain.getHandler());
  53.         return model;
  54.     }
  55. }

 

 

 

 

 

 

这是个JUnit測试类,我们能够new Request对象,来參与測试,太方便了。给request指定訪问的URL,就能够请求目标Action了。

 

Java代码
  1. package test.com.app.user;
  2. import org.junit.Assert;
  3. import org.junit.Test;
  4. import org.springframework.mock.web.MockHttpServletRequest;
  5. import org.springframework.mock.web.MockHttpServletResponse;
  6. import org.springframework.web.servlet.ModelAndView;
  7. import test.JUnitActionBase;
  8. /**
  9. * 说明: 測试OrderAction的样例
  10. *
  11. * @author  赵磊
  12. * @version 创建时间:2011-2-2 下午10:26:55
  13. */
  14. public class TestOrderAction extends JUnitActionBase {
  15.     @Test
  16.     public void testAdd() throws Exception {
  17.     MockHttpServletRequest request = new MockHttpServletRequest();
  18.         MockHttpServletResponse response = new MockHttpServletResponse();
  19.         request.setRequestURI(“/order/add”);
  20.         request.addParameter(“id”, “1002”);
  21.         request.addParameter(“date”, “2010-12-30”);
  22.         request.setMethod(“POST”);
  23.         // 运行URI相应的action
  24.         final ModelAndView mav = this.excuteAction(request, response);
  25.         // Assert logic
  26.         Assert.assertEquals(“order/add”, mav.getViewName());
  27.         String msg=(String)request.getAttribute(“msg”);
  28.         System.out.println(msg);
  29.     }
  30. }

须要说明一下 :由于当前最想版本号的Spring(Test) 3.0.5还不支持@ContextConfiguration的注解式context file注入,所以还须要写个setUp处理下,否则类似于Tiles的载入过程会有错误,由于没有ServletContext。3.1的版本号应该有更好的解决方式,參见: https://jira.springsource.org/browse/SPR-5243 

參考 :http://www.iteye.com/topic/828513

 

 

 

 

十四、转发与重定向

能够通过redirect/forward:url方式转到还有一个Action进行连续的处理。

能够通过redirect:url 防止表单反复提交 。

写法例如以下:

return “forward:/order/add”;

return “redirect:/index.jsp”;

 

 

 

 

 十五、处理ajax请求

 

1、引入以下两个jar包,我用的是1.7.2,好像1.4.2版本号以上都能够,下载地址: http://wiki.fasterxml.com/JacksonDownload

jackson-core-asl-1.7.2.jar

jackson-mapper-asl-1.7.2.jar

 

2、spring的配置文件里要有这一行,才干使用到spring内置支持的json转换。假设你手工把POJO转成json就能够不需要使用spring内置支持的json转换。

<mvc:annotation-driven />

 

3、使用@ResponseBody注解

Java代码
  1. /**
  2.  * ajax測试
  3. * http://127.0.0.1/mvc/order/ajax
  4.  */
  5. @RequestMapping(“/ajax”)
  6. @ResponseBody
  7. public Object ajax(HttpServletRequest request){
  8.     List<String> list=new ArrayList<String>();
  9.     list.add(“电视”);
  10. nbsp;       list.add(“洗衣机”);
  11.     list.add(“冰箱”);
  12.     list.add(“电脑”);
  13.     list.add(“汽车”);
  14.     list.add(“空调”);
  15.     list.add(“自行车”);
  16.     list.add(“饮水机”);
  17.     list.add(“热水器”);
  18.     return list;
  19. }

from:http://www.cnblogs.com/lcchuguo/p/4052521.html?utm_source=tuicool&utm_medium=referral

使用 Spring 进行单元测试

概述

单元测试和集成测试在我们的软件开发整个流程中占有举足轻重的地位,一方面,程序员通过编写单元测试来验证自己程序的有效性,另外一方面,管理者通过持续自动的执行单元测试和分析单元测试的覆盖率等来确保软件本身的质量。这里,我们先不谈单元测试本身的重要性,对于目前大多数的基于 Java 的企业应用软件来说,Spring 已经成为了标准配置,一方面它实现了程序之间的低耦合度,另外也通过一些配置减少了企业软件集成的工作量,例如和 Hibernate、Struts 等的集成。那么,有个问题,在普遍使用 Spring 的应用程序中,我们如何去做单元测试?或者说,我们怎么样能高效的在 Spring 生态系统中实现各种单元测试手段?这就是本文章要告诉大家的事情。

单元测试目前主要的框架包括 Junit、TestNG,还有些 MOCK 框架,例如 Jmock、Easymock、PowerMock 等,这些都是单元测试的利器,但是当把他们用在 Spring 的开发环境中,还是那么高效么?还好,Spring 提供了单元测试的强大支持,主要特性包括:

  • 支持主流的测试框架 Junit 和 TestNG
  • 支持在测试类中使用依赖注入 Denpendency Injection
  • 支持测试类的自动化事务管理
  • 支持使用各种注释标签,提高开发效率和代码简洁性
  • Spring 3.1 更是支持在测试类中使用非 XML 配置方法和基于 Profile 的 bean 配置模式

通过阅读本文,您能够快速的掌握基于 Spring TestContext 框架的测试方法,并了解基本的实现原理。本文将提供大量测试标签的使用方法,通过这些标签,开发人员能够极大的减少编码工作量。OK,现在让我们开始 Spring 的测试之旅吧!


原来我们是怎么做的

这里先展示一个基于 Junit 的单元测试,这个单元测试运行在基于 Spring 的应用程序中,需要使用 Spring 的相关配置文件来进行测试。相关类图如下:

数据库表

假设有一个员工账号表,保存了员工的基本账号信息,表结构如下:

  • ID:整数类型,唯一标识
  • NAME:字符串,登录账号
  • SEX:字符串,性别
  • AGE:字符串,年龄

假设表已经建好,且内容为空。

测试工程目录结构和依赖 jar 包

在 Eclipse 中,我们可以展开工程目录结构,看到如下图所示的工程目录结构和依赖的 jar 包列表:

您需要引入的 jar 包括:

  • cglib-nodep-2.2.3.jar
  • commons-logging.jar
  • hsqldb.jar
  • Junit-4.5.jar
  • log4j-1.2.14.jar
  • Spring-asm-3.2.0.M1.jar
  • Spring-beans-3.2.0.M1.jar
  • Spring-context-3.2.0.M1.jar
  • Spring-core-3.2.0.M1.jar
  • Spring-expression-3.2.0.M1.jar
  • Spring-jdbc-3.2.0.M1.jar
  • Spring-test-3.2.0.M1.jar
  • Spring-tx-3.2.0.M1.jar
  • testng-6.8.jar

其中的 hsqldb 是我们测试用数据库。

图 1. 工程目录结构

图 1. 工程目录结构

类总体介绍

假设我们现在有一个基于 Spring 的应用程序,除了 MVC 层,还包括业务层和数据访问层,业务层有一个类 AccountService,负责处理账号类的业务,其依赖于数据访问层 AccountDao 类,此类提供了基于 Spring Jdbc Template 实现的数据库访问方法,AccountService 和 AccountDao 以及他们之间的依赖关系都是通过 Spring 配置文件进行管理的。

现在我们要对 AccountService 类进行测试,在不使用 Spring 测试方法之前,我们需要这样做:

此类代表账号的基本信息,提供 getter 和 setter 方法。

清单 1. Account.Java
 package domain; 

 public class Account { 
	 public static final String SEX_MALE = "male"; 
	 public static final String SEX_FEMALE = "female"; 
	
	 private int id; 
	 private String name; 
	 private int age; 
	 private String sex; 
     public String toString() { 
	    return String.format("Account[id=%d,name=%s,age:%d,sex:%s]",id,name,age,sex); 
	 } 
	 public int getId() { 
		 return id; 
	 } 
	 public void setId(int id) { 
		 this.id = id; 
	 } 
	 public String getName() { 
		 return name; 
	 } 
	 public void setName(String name) { 
		 this.name = name; 
	 } 
	 public int getAge() { 
		 return age; 
	 } 
	 public void setAge(int age) { 
		 this.age = age; 
	 } 
	 public String getSex() { 
		 return sex; 
	 } 
	 public void setSex(String sex) { 
		 this.sex = sex; 
	 } 
	
     public static Account getAccount(int id,String name,int age,String sex) { 
		 Account acct = new Account(); 
		 acct.setId(id); 
		 acct.setName(name); 
		 acct.setAge(age); 
		 acct.setSex(sex); 
		 return acct; 
	 } 
 }

注意上面的 Account 类有一个 toString() 方法和一个静态的 getAccount 方法,getAccount 方法用于快速获取 Account 测试对象。

这个 DAO 我们这里为了简单起见,采用 Spring Jdbc Template 来实现。

清单 2. AccountDao.Java
 package DAO; 

 import Java.sql.ResultSet; 
 import Java.sql.SQLException; 
 import Java.util.HashMap; 
 import Java.util.List; 
 import Java.util.Map; 

 import org.Springframework.context.ApplicationContext; 
 import org.Springframework.context.support.ClassPathXmlApplicationContext; 
 import org.Springframework.jdbc.core.RowMapper; 
 import org.Springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport; 
 import org.Springframework.jdbc.core.simple.ParameterizedRowMapper; 

 import domain.Account; 

 public class AccountDao extends NamedParameterJdbcDaoSupport { 
	 public void saveAccount(Account account) { 
		 String sql = "insert into tbl_account(id,name,age,sex) " + 
				"values(:id,:name,:age,:sex)"; 
		 Map paramMap = new HashMap(); 
		 paramMap.put("id", account.getId()); 
		 paramMap.put("name", account.getName()); 
		 paramMap.put("age", account.getAge()); 
		 paramMap.put("sex",account.getSex()); 
		 getNamedParameterJdbcTemplate().update(sql, paramMap); 
	 } 
	
	 public Account getAccountById(int id) { 
		 String sql = "select id,name,age,sex from tbl_account where id=:id"; 
		 Map paramMap = new HashMap(); 
		 paramMap.put("id", id); 
		 List<Account> matches = getNamedParameterJdbcTemplate().query(sql, 
		 paramMap,new ParameterizedRowMapper<Account>() { 
					 @Override 
					 public Account mapRow(ResultSet rs, int rowNum) 
							 throws SQLException { 
						 Account a = new Account(); 
						 a.setId(rs.getInt(1)); 
						 a.setName(rs.getString(2)); 
						 a.setAge(rs.getInt(3)); 
						 a.setSex(rs.getString(4)); 
						 return a; 
					 } 
			
		 }); 
		 return matches.size()>0?matches.get(0):null; 
	 } 
	
 }

AccountDao 定义了几个账号对象的数据库访问方法:

  • saveAccount:负责把传入的账号对象入库
  • getAccountById:负责根据 Id 查询账号
清单 3. AccountService.Java
 package service; 

 import org.apache.commons.logging.Log; 
 import org.apache.commons.logging.LogFactory; 
 import org.Springframework.beans.factory.annotation.Autowired; 

 import DAO.AccountDao; 
 import domain.Account; 

 public class AccountService { 
	 private static final Log log = LogFactory.getLog(AccountService.class); 
	
	 @Autowired 
	 private AccountDao accountDao; 
	
	 public Account getAccountById(int id) { 
		 return accountDao.getAccountById(id); 
	 } 
	
	 public void insertIfNotExist(Account account) { 
		 Account acct = accountDao.getAccountById(account.getId()); 
		 if(acct==null) { 
			 log.debug("No "+account+" found,would insert it."); 
             accountDao.saveAccount(account); 
		 } 
		 acct = null; 
	 } 
		
 }

AccountService 包括下列方法:

  • getAccountById:根据 Id 查询账号信息
  • insertIfNotExist:根据传入的对象插入数据库

其依赖的 DAO 对象 accountDao 是通过 Spring 注释标签 @Autowired 自动注入的。

上述几个类的依赖关系是通过 Spring 进行管理的,配置文件如下:

清单 4. Spring 配置文件
 <beans xmlns="http://www.Springframework.org/schema/beans"
	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.Springframework.org/schema/context"
 xsi:schemaLocation="http://www.Springframework.org/schema/beans 
 http://www.Springframework.org/schema/beans/Spring-beans-3.0.xsd 
 http://www.Springframework.org/schema/context 
 http://www.Springframework.org/schema/context/Spring-context-3.0.xsd "> 
	
 <context:annotation-config/> 
 <bean id="datasource" class="
 org.Springframework.jdbc.datasource.DriverManagerDataSource"> 
		 <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> 
		 <property name="url" value="jdbc:hsqldb:hsql://localhost" /> 
		 <property name="username" value="sa" /> 
		 <property name="password" value="" /> 
	 </bean> 
	 <bean id="initer" init-method="init" class="service.Initializer"> 
	 </bean> 
 <bean id="accountDao" depends-on="initer" class="DAO.AccountDao"> 
		 <property name="dataSource" ref="datasource" /> 
	 </bean> 
 <bean id="accountService" class="service.AccountService"> 
	 </bean> 
 </beans>

注意其中的“<context:annotation-config/>”的作用,这个配置启用了 Spring 对 Annotation 的支持,这样在我们的测试类中 @Autowired 注释才会起作用(如果用了 Spring 测试框架,则不需要这样的配置项,稍后会演示)。另外还有一个 accountDao 依赖的 initer bean, 这个 bean 的作用是加载 log4j 日志环境,不是必须的。

另外还有一个要注意的地方,就是 datasource 的定义,由于我们使用的是 Spring Jdbc Template,所以只要定义一个 org.Springframework.jdbc.datasource.DriverManagerDataSource 类型的 datasource 即可。这里我们使用了简单的数据库 HSQL、Single Server 运行模式,通过 JDBC 进行访问。实际测试中,大家可以选择 Oracle 或者 DB2、Mysql 等。

好,万事具备,下面我们来用 Junit4 框架测试 accountService 类。代码如下:

清单 5. AccountServiceOldTest.Java
 package service; 

 import static org.Junit.Assert.assertEquals; 

 import org.Junit.BeforeClass; 
 import org.Junit.Test; 
 import org.Springframework.context.ApplicationContext; 
 import org.Springframework.context.support.ClassPathXmlApplicationContext; 

 import domain.Account; 


 public class AccountServiceOldTest { 
	 private static AccountService service; 
	
	 @BeforeClass 
	 public static void init() { 
		 ApplicationContext 
 context = new ClassPathXmlApplicationContext("config/Spring-db-old.xml"); 
		 service = (AccountService)context.getBean("accountService"); 
	 } 	
	
	 @Test 
	 public void testGetAcccountById() { 
 Account acct = Account.getAccount(1, "user01", 18, "M"); 
		 Account acct2 = null; 
		 try { 
 service.insertIfNotExist(acct); 
			 acct2 = service.getAccountById(1); 
			 assertEquals(acct, acct2); 
		 } catch (Exception ex) { 
			 fail(ex.getMessage()); 
		 } finally { 
			 service.removeAccount(acct); 
		 } 
 } 
 }

注意上面的 Junit4 注释标签,第一个注释标签 @BeforeClass,用来执行整个测试类需要一次性初始化的环境,这里我们用 Spring 的 ClassPathXmlApplicationContext 从 XML 文件中加载了上面定义的 Spring 配置文件,并从中获得了 accountService 的实例。第二个注释标签 @Test 用来进行实际的测试。

测试过程:我们先获取一个 Account 实例对象,然后通过 service bean 插入数据库中,然后通过 getAccountById 方法从数据库再查询这个记录,如果能获取,则判断两者的相等性;如果相同,则表示测试成功。成功后,我们尝试删除这个记录,以利于下一个测试的进行,这里我们用了 try-catch-finally 来保证账号信息会被清除。

执行测试:(在 Eclipse 中,右键选择 AccountServiceOldTest 类,点击 Run as Junit test 选项),得到的结果如下:

执行测试的结果

在 Eclipse 的 Junit 视图中,我们可以看到如下的结果:

图 2. 测试的结果

图 2. 测试的结果

对于这种不使用 Spring test 框架进行的单元测试,我们注意到,需要做这些工作:

  • 在测试开始之前,需要手工加载 Spring 的配置文件,并获取需要的 bean 实例
  • 在测试结束的时候,需要手工清空搭建的数据库环境,比如清除您插入或者更新的数据,以保证对下一个测试没有影响

另外,在这个测试类中,我们还不能使用 Spring 的依赖注入特性。一切都靠手工编码实现。好,那么我们看看 Spring test 框架能做到什么。

首先我们修改一下 Spring 的 XML 配置文件,删除 <context:annotation-config/> 行,其他不变。

清单 6. Spring-db1.xml
 <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 
 http://www.Springframework.org/schema/beans/Spring-beans-3.2.xsd"> 
 <bean id="datasource" 
 class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> 
		 <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> 
		 <property name="url" value="jdbc:hsqldb:hsql://localhost" /> 
		 <property name="username" value="sa"/> 
		 <property name="password" value=""/> 
	 </bean> 
 <bean id="transactionManager" 
 class="org.Springframework.jdbc.datasource.DataSourceTransactionManager"> 
 		 <property name="dataSource" ref="datasource"></property> 
 	 </bean> 
 	 <bean id="initer" init-method="init" class="service.Initializer"> 
 	 </bean> 
 <bean id="accountDao" depends-on="initer" class="DAO.AccountDao"> 
 		 <property name="dataSource" ref="datasource"/> 
 	 </bean> 
 	 <bean id="accountService" class="service.AccountService"> 
 	 </bean> 
 </beans>

其中的 transactionManager 是 Spring test 框架用来做事务管理的管理器。

清单 7. AccountServiceTest1.Java
 package service; 
 import static org.Junit.Assert.assertEquals; 

 import org.Junit.Test; 
 import org.Junit.runner.RunWith; 
 import org.Springframework.beans.factory.annotation.Autowired; 
 import org.Springframework.test.context.ContextConfiguration; 
 import org.Springframework.test.context.Junit4.SpringJUnit4ClassRunner; 
 import org.Springframework.transaction.annotation.Transactional; 

 import domain.Account; 

 @RunWith(SpringJUnit4ClassRunner.class) 
 @ContextConfiguration("/config/Spring-db1.xml") 
 @Transactional 
 public class AccountServiceTest1 { 
	 @Autowired 
	 private AccountService service; 
	
	 @Test 
	 public void testGetAcccountById() { 
 Account acct = Account.getAccount(1, "user01", 18, "M"); 
		 service.insertIfNotExist(acct); 
		 Account acct2 = service.getAccountById(1); 
		 assertEquals(acct,acct2); 
	 } 
 }

对这个类解释一下:

  • @RunWith 注释标签是 Junit 提供的,用来说明此测试类的运行者,这里用了 SpringJUnit4ClassRunner,这个类是一个针对 Junit 运行环境的自定义扩展,用来标准化在 Spring 环境中 Junit4.5 的测试用例,例如支持的注释标签的标准化
  • @ContextConfiguration 注释标签是 Spring test context 提供的,用来指定 Spring 配置信息的来源,支持指定 XML 文件位置或者 Spring 配置类名,这里我们指定 classpath 下的 /config/Spring-db1.xml 为配置文件的位置
  • @Transactional 注释标签是表明此测试类的事务启用,这样所有的测试方案都会自动的 rollback,即您不用自己清除自己所做的任何对数据库的变更了
  • @Autowired 体现了我们的测试类也是在 Spring 的容器中管理的,他可以获取容器的 bean 的注入,您不用自己手工获取要测试的 bean 实例了
  • testGetAccountById 是我们的测试用例:注意和上面的 AccountServiceOldTest 中相同的测试方法的对比,这里我们不用再 try-catch-finally 了,事务管理自动运行,当我们执行完成后,所有相关变更会被自动清除

执行结果

在 Eclipse 的 Junit 视图中,我们可以看到如下的结果:

图 3. 执行结果

图 3. 执行结果

小结

如果您希望在 Spring 环境中进行单元测试,那么可以做如下配置:

  • 继续使用 Junit4 测试框架,包括其 @Test 注释标签和相关的类和方法的定义,这些都不用变
  • 您需要通过 @RunWith(SpringJUnit4ClassRunner.class) 来启动 Spring 对测试类的支持
  • 您需要通过 @ContextConfiguration 注释标签来指定 Spring 配置文件或者配置类的位置
  • 您需要通过 @Transactional 来启用自动的事务管理
  • 您可以使用 @Autowired 自动织入 Spring 的 bean 用来测试

另外您不再需要:

  • 手工加载 Spring 的配置文件
  • 手工清理数据库的每次变更
  • 手工获取 application context 然后获取 bean 实例

Spring 测试注释标签

我们已经看到利用 Spring test framework 来进行基于 Junit4 的单元测试是多么的简单,下面我们来看一下前面遇到的各种注释标签的一些可选用法。

@ContextConfiguration 和 @Configuration 的使用

刚才已经介绍过,可以输入 Spring xml 文件的位置,Spring test framework 会自动加载 XML 文件,得到 application context,当然也可以使用 Spring 3.0 新提供的特性 @Configuration,这个注释标签允许您用 Java 语言来定义 bean 实例,举个例子:

现在我们将前面定义的 Spring-db1.xml 进行修改,我们希望其中的三个 bean:initer、accountDao、accountService 通过配置类来定义,而不是 XML,则我们需要定义如下配置类:

注意:如果您想使用 @Configuration,请在 classpath 中加入 cglib 的 jar 包(cglib-nodep-2.2.3.jar),否则会报错。

清单 8. SpringDb2Config.Java
 package config; 

 import org.Springframework.beans.factory.annotation.Autowired; 
 import org.Springframework.context.annotation.Bean; 
 import org.Springframework.context.annotation.Configuration; 
 import org.Springframework.jdbc.datasource.DriverManagerDataSource; 

 import service.AccountService; 
 import service.Initializer; 
 import DAO.AccountDao; 

 @Configuration 
 public class SpringDb2Config { 
	 private @Autowired DriverManagerDataSource datasource; 
	 @Bean 
	 public Initializer initer() { 
		 return new Initializer(); 
	 } 
	
	 @Bean 
	 public AccountDao accountDao() { 
 AccountDao DAO = new AccountDao(); 
 DAO.setDataSource(datasource); 
 return DAO; 
	 } 
	
	 @Bean 
	 public AccountService accountService() { 
 return new AccountService(); 
	 } 
 }

注意上面的注释标签:

  • @Configuration:表明这个类是一个 Spring 配置类,提供 Spring 的 bean 定义,实际效果等同于 XML 配置方法
  • @Bean:表明这个方法是一个 bean 的定义,缺省情况下,方法名称就是 bean 的 Id
  • @Autowired:这个 datasource 采用自动注入的方式获取

注意,我们采用的是 XML+config bean 的方式进行配置,这种方式比较符合实际项目的情况。相关的 Spring 配置文件也要做变化,如下清单所示:

清单 9. Spring-db2.xml
 <beans xmlns="http://www.Springframework.org/schema/beans"
	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.Springframework.org/schema/context"
 xsi:schemaLocation="http://www.Springframework.org/schema/beans 
 http://www.Springframework.org/schema/beans/Spring-beans-3.0.xsd 
 http://www.Springframework.org/schema/context 
 http://www.Springframework.org/schema/context/Spring-context-3.0.xsd"> 
 <context:annotation-config/> 
 <bean id="datasource" 
 class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> 
		 <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> 
		 <property name="url" value="jdbc:hsqldb:hsql://localhost" /> 
		 <property name="username" value="sa"/> 
		 <property name="password" value=""/> 
	 </bean> 
 <bean id="transactionManager" 
          class="org.Springframework.jdbc.datasource.DataSourceTransactionManager"> 
 		 <property name="dataSource" ref="datasource"></property> 
 	 </bean> 
 	
 	 <bean class="config.SpringDb2Config"/> 
 </beans>

注意里面的 context 命名空间的定义,如代码中黑体字所示。另外还必须有 <context:annotaiton-config/> 的定义,这个定义允许采用注释标签的方式来控制 Spring 的容器,最后我们看到 beans 已经没有 initer、accountDao 和 accountService 这些 bean 的定义,取而代之的是一个 SpringDb2Config bean 的定义,注意这个 bean 没有名称,因为不需要被引用。

现在有了这些配置,我们的测试类只要稍稍修改一下,即可实现加载配置类的效果,如下:

 @ContextConfiguration("/config/Spring-db2.xml")

通过上面的配置,测试用例就可以实现加载 Spring 配置类,运行结果也是成功的 green bar。

@DirtiesContext

缺省情况下,Spring 测试框架一旦加载 applicationContext 后,将一直缓存,不会改变,但是,

由于 Spring 允许在运行期修改 applicationContext 的定义,例如在运行期获取 applicationContext,然后调用 registerSingleton 方法来动态的注册新的 bean,这样的情况下,如果我们还使用 Spring 测试框架的被修改过 applicationContext,则会带来测试问题,我们必须能够在运行期重新加载 applicationContext,这个时候,我们可以在测试类或者方法上注释:@DirtiesContext,作用如下:

  • 如果定义在类上(缺省),则在此测试类运行完成后,重新加载 applicationContext
  • 如果定义在方法上,即表示测试方法运行完成后,重新加载 applicationContext

@TransactionConfiguration 和 @Rollback

缺省情况下,Spring 测试框架将事务管理委托到名为 transactionManager 的 bean 上,如果您的事务管理器不是这个名字,那需要指定 transactionManager 属性名称,还可以指定 defaultRollback 属性,缺省为 true,即所有的方法都 rollback,您可以指定为 false,这样,在一些需要 rollback 的方法,指定注释标签 @Rollback(true)即可。


对 Junit4 的注释标签支持

看了上面 Spring 测试框架的注释标签,我们来看看一些常见的基于 Junit4 的注释标签在 Spring 测试环境中的使用方法。

@Test(expected=…)

此注释标签的含义是,这是一个测试,期待一个异常的发生,期待的异常通过 xxx.class 标识。例如,我们修改 AccountService.Java 的 insertIfNotExist 方法,对于传入的参数如果为空,则抛出 IllegalArgumentException,如下:

 public void insertIfNotExist(Account account) { 
	 if(account==null) 
		 throw new IllegalArgumentException("account is null"); 
	 Account acct = accountDao.getAccountById(account.getId()); 
	 if(acct==null) { 
		 log.debug("No "+account+" found,would insert it."); 
		 accountDao.saveAccount(account); 
	 } 
	 acct = null; 
 }

然后,在测试类中增加一个测试异常的方法,如下:

 @Test(expected=IllegalArgumentException.class) 
 public void testInsertException() { 
	 service.insertIfNotExist(null); 
 }

运行结果是 green bar。

@Test(timeout=…)

可以给测试方法指定超时时间(毫秒级别),当测试方法的执行时间超过此值,则失败。

比如在 AccountService 中增加如下方法:

 public void doSomeHugeJob() { 
	 try { 
		 Thread.sleep(2*1000); 
	 } catch (InterruptedException e) { 
	 } 
 }

上述方法模拟任务执行时间 2 秒,则测试方法如下:

 @Test(timeout=3000) 
 public void testHugeJob() { 
	 service.doSomeHugeJob(); 
 }

上述测试方法期待 service.doSomeHugeJob 方法能在 3 秒内结束,执行测试结果是 green bar。

@Repeat

通过 @Repeat,您可以轻松的多次执行测试用例,而不用自己写 for 循环,使用方法:

 @Repeat(3) 
 @Test(expected=IllegalArgumentException.class) 
 public void testInsertException() { 
	 service.insertIfNotExist(null); 
 }

这样,testInsertException 就能被执行 3 次。


在测试类中基于 profile 加载测试 bean

从 Spring 3.2 以后,Spring 开始支持使用 @ActiveProfiles 来指定测试类加载的配置包,比如您的配置文件只有一个,但是需要兼容生产环境的配置和单元测试的配置,那么您可以使用 profile 的方式来定义 beans,如下:

清单 10. Spring-db.xml
 <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 
 http://www.Springframework.org/schema/beans/Spring-beans-3.2.xsd"> 
 	 <beans profile="test"> 
 <bean id="datasource" 
 class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> 
 <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> 
	 <property name="url" value="jdbc:hsqldb:hsql://localhost" /> 
	 <property name="username" value="sa"/> 
	 <property name="password" value=""/> 
	 </bean> 
</beans> 
	
<beans profile="production"> 
 <bean id="datasource" 
 class="org.Springframework.jdbc.datasource.DriverManagerDataSource"> 
	 <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> 
	 <property name="url" value="jdbc:hsqldb:hsql://localhost/prod" /> 
	 <property name="username" value="sa"/> 
	 <property name="password" value=""/> 
	 </bean> 
 </beans> 
 <beans profile="test,production"> 
 <bean id="transactionManager" 
     class="org.Springframework.jdbc.datasource.DataSourceTransactionManager"> 
	 <property name="dataSource" ref="datasource"></property> 
	 </bean> 
	 <bean id="initer" init-method="init" class="service.Initializer"> 
	 </bean> 
 <bean id="accountDao" depends-on="initer" class="DAO.AccountDao"> 
	 		 <property name="dataSource" ref="datasource"/> 
	 	 </bean> 
	 	
	 	 <bean id="accountService" class="service.AccountService"> 
	 	 </bean> 
	 	 <bean id="envSetter" class="EnvSetter"/> 
 	 </beans> 
 </beans>

上面的定义,我们看到:

  • 在 XML 头中我们引用了 Spring 3.2 的 beans 定义,因为只有 Spring 3.2+ 才支持基于 profile 的定义
  • 在 <beans> 根节点下可以嵌套 <beans> 定义,要指定 profile 属性,这个配置中,我们定义了两个 datasource,一个属于 test profile,一个输入 production profile,这样,我们就能在测试程序中加载 test profile,不影响 production 数据库了
  • 在下面定义了一些属于两个 profile 的 beans,即 <beans profile=”test,production”> 这样方便重用一些 bean 的定义,因为这些 bean 在两个 profile 中都是一样的
清单 11. AccountServiceTest.Java
 @RunWith(SpringJUnit4ClassRunner.class) 
 @ContextConfiguration("/config/Spring-db.xml") 
 @Transactional 
 @ActiveProfiles("test") 
 public class AccountServiceTest { 
 ... 
 }

注意上面的 @ActiveProfiles,可以指定一个或者多个 profile,这样我们的测试类就仅仅加载这些名字的 profile 中定义的 bean 实例。


对 TestNG 的支持

Spring 2.5 以后,就开始支持 TestNG 了,支持的方法包括:

  • 将您的 TestNG 测试类继承 Spring 的测试父类:AbstractTransactionalTestNGSpringContextTests 或者 AbstractTestNGSpringContextTests,这样您的 TestNG 测试类内部就可以访问 applicationContext 成员变量了
  • 不继承 Spring 父类,在测试类上使用 @TestExecutionListeners 注释标签,可以引入的监听器包括
    • DependencyInjectionTestExecutionListener:使得测试类拥有依赖注入特性
    • DirtiesContextTestExecutionListener:使得测试类拥有更新 applicationContext 能力
    • TransactionalTestExecutionListener:使得测试类拥有自动的事务管理能力

这里我们演示一下如何使用 Spring 提供的 TestNG 父类来进行测试。

清单 12. AccountServiceTestNGTest.Java
 package testng; 

 import static org.Junit.Assert.assertEquals; 

 import org.Springframework.beans.factory.annotation.Autowired; 
 import org.Springframework.test.context.ActiveProfiles; 
 import org.Springframework.test.context.ContextConfiguration; 
 import org.Springframework.test.context.testng. 
 AbstractTransactionalTestNGSpringContextTests; 
 import org.Springframework.transaction.annotation.Transactional; 

 import service.AccountService; 
 import domain.Account; 

 @ContextConfiguration("/config/Spring-db.xml") 
 @Transactional 
 @ActiveProfiles("test") 
 public class AccountServiceTestNGTest extends 
 AbstractTransactionalTestNGSpringContextTests { 
	 @Autowired 
	 private AccountService service; 
	
	 @org.testng.annotations.Test 
	 public void testGetAcccountById() { 
		 Account acct = Account.getAccount(1, "user01", 18, "M"); 
		 service.insertIfNotExist(acct); 
		 Account acct2 = service.getAccountById(1); 
		 assertEquals(acct,acct2); 
	 } 
 }

执行测试,我们将看到测试成功。

图 4. 测试成功

图 4. 测试成功

搜索数据库对应的表,我们看到里面没有数据,说明自动事务起作用了。


基本原理

Spring test framework 主要位于 org.Springframework.test.context 包中,主要包括下面几个类:

图 5. Spring 测试框架类图(查看大图

图 5. Spring 测试框架类图

  • TestContextManager:主要的入口类,提供 TestContext 实例的管理,负责根据各种事件来通知测试监听器
  • TestContext:实体类,提供访问 Spring applicatoin context 的能力,并负责缓存 applicationContext
  • TestExecutionListener:测试监听器,提供依赖注入、applicationContext 缓存和事务管理等能力
  • ContextLoader:负责根据配置加载 Spring 的 bean 定义,以构建 applicationContext 实例对象
  • SmartContextLoader:Spring 3.1 引入的新加载方法,支持按照 profile 加载

Spring 通过 AOP hook 了测试类的实例创建、beforeClass、before、after、afterClass 等事件入口,执行顺序主要如下:

图 6. Spring 测试框架执行序列图(查看大图

图 6. Spring 测试框架执行序列图

  • 测试执行者开始执行测试类,这个时候 Spring 获取消息,自动创建 TestContextManager 实例
  • TestContextManager 会创建 TestContext,以记录当前测试的上下文信息,TestContext 则通过 ContextLoader 来获取 Spring ApplicationContext 实例
  • 当测试执行者开始执行测试类的 BeforeClass、Before、After、AfterClass 的时候,TestContextManager 将截获事件,发通知给对应的 TestExecutionListener

总结

根据上面的例子和介绍,我们可以看到,Spring 测试框架的主要特点如下:

  • 完美的支持了 Junit4(提供特别的 SpringJunit4ClassRunner),比较好的支持了 TestNG
  • 在支持原有单元测试能力的基础上,通过各种监听器,支持了测试类的依赖注入、对 Spring applicationContext 的访问以及事务管理能力,为使用 Spring 架构的应用程序的测试带来了极大的便利性
  • Spring 3.1 引入的基于 profile 的加载能力使得测试环境和正式环境可以在一个 XML 定义中完美的结合

总之,如果您的程序中使用了 Spring,且对用 Junit 或者 testNG 来对他们进行单元测试感到力不从心,可以考虑使用 Spring test framework,它将使您的应用程序的质量上一个新的台阶。

参考资料

学习

讨论

  • 加入 developerWorks 中文社区:查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

其他参考:
Using Spring MVC Test to unit test multipart POST request
Unit Testing of Spring MVC Controllers: REST API

from:http://www.ibm.com/developerworks/cn/java/j-lo-springunitest/index.html