2009年8月4日星期二

[转]接待领导慰问的“专业户”农民(太牛了)

    有一个专门接待上级领导慰问的“专业户”,他就是安徽省阜南县王家坝镇农民郑继超。据不完全统计,在短短3个月内,到过郑继超老伯家的领导有(按职务由低到高排列,乡、村领导不算):    阜南县副县长李国庆
    阜阳市政府秘书长李子鹏
    市政协副主席陶克贵
    阜阳市副市长胡明莹
    阜阳市副市长刘绍太
    阜阳市副市长杜长平
    阜阳市人大副主任储金水
    阜阳市委常委、副市长王海彦
    阜阳市委常委亓龙,阜阳市委常委、原常务副市长(现为市人大常委会副主任)宋家伟
    阜阳市委副书记孟庆银
    阜阳市委副书记、市长孙云飞
    省民政厅厅长李宏塔
    安徽省政府副秘书长张韶春
    阜阳市委书记、市人大常委会主任宋卫平
    安徽省政府秘书长张俊
    安徽省人大常委会副主任、原阜阳市委书记(现已不再兼此职)胡连松
    安徽省委常委、副省长赵树丛
    中央直属机关工委副书记赵凯
    安徽省委副书记、代省长王三运
    安徽省委书记王金山
    中国人民共和国国家主席胡主席

    2008年1月12日下午,胡锦涛顶风冒雪,专程前往淮河 王家坝闸视察。去年夏季,为了淮河防汛大局,蓄洪区群众作出了巨大牺性。总书记惦念着蓄洪区的乡亲们,特意来到阜南县王家坝镇李郢村郑台孜庄台看望。胡锦 涛实地察看了庄台硬化护坡,察看了村里张贴的蓄洪补偿公示表,并来到村民郑继超家里。
    一进屋,总书记就拉着主人的手,深情地说:“去年夏天淮河发大水,乡亲们遭灾了。 现在已是寒冬腊月,你们生产生活上还有哪些困难,我一直放心不下,今天特地来看看。”郑继超告诉总书记,依靠党委和政府的帮助,自己家盖起了新房,生活也 都有了保障。“国家给的蓄洪补偿款发到手了吗?”总书记问。 “发到手了,在这儿呢!”郑继超说着,从胸口的衣兜里掏出一个红色存折。胡锦涛接过存折,一边看一边念着款数,念完后笑着对郑 继超说:“这样,我心里就踏实了。” 离开郑继超家时,看到他家院子里架起了自来水管,胡锦涛便走过去,拧开水龙头,用手捧起冰凉的水,喝了一口。总书记心系群众的这一幕,深深打动了在场的所 有人。来源 新华网  2007年12月22日
    更多报道可见:
http://www.ahxf.gov.cn/shownew.asp?ID=53372
http://www.fy.gov.cn/html/20071205/200712051052274638.shtml
http://www.fyradio.com.cn/Html/fynews/20071206192005.html
http://www.hf365.com/html/01/02/20071206/78602.htm
http://www.kpwww.com/readnews.asp?newsid=1892
http://www.glmj.gov.cn/news/xuanjiao/fengcai/2007/12/1802.htm
http://www.ahxf.gov.cn/shownews.asp?ID=55868
http://www.hwcc.com.cn/newsdisplay/NewsDisplay.asp?id=184526
http://www.china.com.cn/chinese/zhuanti/qkjc/797823.htm
http://www.cctv.com/video/xinwen ... 0_20080114_12.shtml

2009年8月3日星期一

[转]spring jar包详细介绍

spring.jar是包含有完整发布的单个jar包,spring.jar中包含除了spring-mock.jar里所包含的内容外其它所 有jar包的内容,因为只有在开发环境下才会用到spring-mock.jar来进行辅助测试,正式应用系统中是用不得这些类的。
 
  除了spring.jar文件,Spring还包括有其它13个独立的jar包,各自包含着对应的Spring组件,用户可以根据自己的需要来选择组合自己的jar包,而不必引入整个spring.jar的所有类文件。
 
(1)   spring-core.jar
 
  这个jar文件包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
 
(2) spring-beans.jar
     
        这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI支持,引入spring-core.jar及spring- beans.jar文件就可以了。
 
(3) spring-aop.jar
     
        这个jar文件包含在应用中使用Spring的AOP特性时所需的类。使用基于AOP的Spring特性,如声明型事务管理(Declarative Transaction Management),也要在应用里包含这个jar包。
 
(4) spring-context.jar
 
这个jar文件为Spring核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI所需的全部类,UI方面的用来与模板(Templating)引擎如 Velocity、FreeMarker、JasperReports集成的类,以及校验Validation方面的相关类。
 
(5) spring-dao.jar
 
  这个jar文件包含Spring DAO、Spring Transaction进行数据访问的所有类。为了使用声明型事务支持,还需在自己的应用里包含spring-aop.jar。
 
(6) spring-hibernate.jar
 
  这个jar文件包含Spring对Hibernate 2及Hibernate 3进行封装的所有类。
 
(7) spring-jdbc.jar
 
  这个jar文件包含对Spring对JDBC数据访问进行封装的所有类。
 
(8) spring-orm.jar
 
这个jar文件包含Spring对DAO特性集进行了扩展,使其支持 iBATIS、JDO、OJB、TopLink,因为Hibernate已经独立成包了,现在不包含在这个包里了。这个jar文件里大部分的类都要依赖 spring-dao.jar里的类,用这个包时你需要同时包含spring-dao.jar包。
 
(9) spring-remoting.jar
 
  这个jar文件包含支持EJB、JMS、远程调用Remoting(RMI、Hessian、Burlap、Http Invoker、JAX-RPC)方面的类。
 
(10) spring-support.jar
 
  这个jar文件包含支持缓存Cache(ehcache)、JCA、JMX、邮件服务(Java Mail、COS Mail)、任务计划Scheduling(Timer、Quartz)方面的类。
 
(11) spring-web.jar
 
  这个jar文件包含Web应用开发时,用到Spring框架时所需的核心类,包括自动载入WebApplicationContext特性的类、Struts与JSF集成类、文件上传的支持类、Filter类和大量工具辅助类。
 
(12) spring-webmvc.jar
 
这个jar文件包含Spring MVC框架相关的所有类。包含国际化、标签、Theme、视图展现的FreeMarker、JasperReports、Tiles、Velocity、 XSLT相关类。当然,如果你的应用使用了独立的MVC框架,则无需这个JAR文件里的任何类。
 
(13) spring-mock.jar
 
  这个jar文件包含Spring一整套mock类来辅助应用的测试。Spring测试套件使用了其中大量mock类,这样测试就更加简单。模拟HttpServletRequest和HttpServletResponse类在Web应用单元测试是很方便的。
 
如何选择这些发布包,决定选用哪些发布包其实相当简单。如果你正在构建Web应用并将全程使用Spring,那么最好就使用单个全部的 spring.jar文件;如果你的应用仅仅用到简单的Inversion of Control / Dependency Injection(IoC/DI)容器,那么只需spring-core.jar与spring-beans.jar即可;如果你对发布的大小要求很 高,那么就得精挑细选了,只取包含自己所需特性的jar文件了。采用独立的发布包你可以避免包含自己的应用不需要的全部类。当然你可以采用其它的一些工具 来设法令整个应用包变小,节省空间的重点在于准确地找出自己所需的Spring依赖类,然后合并所需的类与包就可以了。Eclispe有个插件叫 ClassPath Helper可以帮你找找所依赖的类。
 
Spring包依赖说明:
1)      spring-core.jar需commons-collections.jar,spring-core.jar是以下其它各个的基本。
2)      spring-beans.jar需spring-core.jar,cglib-nodep-2.1_3.jar
3)      spring-aop.jar需spring-core.jar,spring-beans.jar,cglib-nodep-2.1_3.jar,aopalliance.jar
4)      spring-context.jar需spring-core.jar,spring-beans.jar,spring-aop.jar,commons-collections.jar,aopalliance.jar
5)      spring-dao.jar需spring-core.jar,spring-beans.jar,spring-aop.jar,spring-context.jar
6)      spring-jdbc.jar需spring-core.jar,spring-beans.jar,spring-dao.jar
7)      spring-web.jar需spring-core.jar,spring-beans.jar,spring-context.jar
8)      spring-webmvc.jar需spring-core.jar/spring-beans.jar/spring-context.jar/spring-web.jar
9)      spring -hibernate.jar需spring-core.jar,spring-beans.jar,spring-aop.jar,spring- dao.jar,spring-jdbc.jar,spring-orm.jar,spring-web.jar,spring-webmvc.jar
10) spring-orm.jar需spring-core.jar,spring-beans.jar,spring-aop.jar,spring-dao.jar,spring-jdbc.jar,spring-web.jar,spring-webmvc.jar
11) spring -remoting.jar需spring-core.jar,spring-beans.jar,spring-aop.jar,spring- dao.jar,spring-context.jar,spring-web.jar,spring-webmvc.jar
12) spring-support.jar需spring-core.jar,spring-beans.jar,spring-aop.jar,spring-dao.jar,spring-context.jar,spring-jdbc.jar
13) spring-mock.jar需spring-core.jar,spring-beans.jar,spring-dao.jar,spring-context.jar,spring-jdbc.jar


原文出处:http://blog.csdn.net/Pointer_v/archive/2007/10/30/1857078.aspx

Vista下配置MySQL

Vista下安装MySQL之后,无法正常启动快速配置程序。需要做些修改。

以下是转的方法:

1、去官方下载MYSQL 5.0。
  2、修改防火墙的设置,在允许例外里添加新的端口,名字为mysql,开放的端口为TCP 3306。

  这个大家应该都会,在Vista控制面板的Windows防火墙里面,点击“更改设置”-“例外”,点击“添加端口”就可以。  

 
    3、安装MYSQL 5.0,可以自己修改安装路径和组件等。
  4、安装完成后,Mysql server instance config wizard应该是启动不了的,下面的工作就是为了解决这个问题的,因为这是配置Mysql的唯一途径。
  5、下载Resource Hacker得简体中文版(如果你看的懂英文,那就直接下原版的)。
  6、解压rh_chinese_big5.zip ,执行 ResHacker.exe
  7、打开MySQLInstanceConfig.exe (在安� MySQL 的bin 目录中) ,可以用鼠标直接把exe等资源文件拖到Resource Hacker窗体上面。
  8、左侧展开「24」→「1」→「1033」
  9、右侧找到 这行
  10、将 level="asAdministrator" 改成 level="requireAdministrator"
  11、重新编译
  12、保存后关闭 (如果不能保存,另存在 bin 目录外,然后覆盖回�也可以)
  13、这时MySQLInstanceConfig.exe,就可以正常的配置了。

[转]DWR中文文档.PDF

DWR中文文档(PDF)下载

2009年8月2日星期日

[转]AJAX和REST 第一部分

服务器端 Web 应用程序因采用富应用程序模型和交付个性化内容而具备了融入式(immersive) 的特点,这种特点越突出,应用程序架构对 Web 架构风格 REST(Representational State Transfer)的违背就越多。这种违背会降低应用程序的可伸缩性,增加系统复杂性。通过与 REST 相互协调,Ajax 架构将使融入式 Web 应用程序消除这些负面影响,尽享 REST 那些出色的特性。

在短短 15 年中,World Wide Web 已经从一项研究实验成长为现代社会的技术支柱。最初发明 Web 的目的是使人们可以轻松发布和链接信息,现在它已经发展为软件应用程序的可行平台。但随着应用程序通过使用富应用程序模型和生成个性化内容而获得了更多的 融入性,它们的架构对 Web 架构风格 REST(Representational State Transfer)的违背也越来越多。这种违背会降低应用程序的可伸缩性,增加系统复杂性。

新兴的 Ajax Web 客户机架构风格让融入式 Web 应用程序与 REST 架构风格协调一致。使它们可以尽享 REST 那些出色的特性,同时又消除了应用程序违背 REST 准则时带来的不良特性。本文将介绍为融入式 Web 应用程序成功结合 Ajax 和 REST 的方法与原因。

请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何新信息都能在这里找到。

REST:Web 架构

尽管 World Wide Web 是在数十年的相关研究基础上建立起来的,但它的有效诞生日期是 1990 年 12 月,当时 Tim Berners-Lee 完成了 Web 主要组件的工作原型:统一资源标识符(URI)、HTTP、HTML、浏览器和服务器。Web 被迅猛采用,远远超过了先驱者们的预期。在 Roy Fielding 最出名的系列文章中(请参看 参考资料),他描述了自己当时的心情:

“尽管对其成功感到兴奋不已,但是 Internet 开发者社区逐渐开始担心,Web 使用的这种快速增长,以及早期 HTTP 的一些拙劣的网络特性,会快速压倒 Internet 基础设施所能承担的容量,从而导致突然的崩塌。”

“一种架构在设计时就应该考虑到一组特性,让它可以满足甚至超越系统的需求。忽略这些特性可能会导致后期变更干扰整个架构,就像是使用一扇落地窗去取代承重墙会破坏整个建筑结构的可靠性。” —— Roy Fielding

Fielding 和其他人对 Web 架构及其是否能够足以支持各种扩展和用法重新进行了审视。这种重新审视的有形结果包括更新诸如 URI 和 HTTP 之类的一些重要标准。这种重新审视还获得了一些无形但却非常有意义的结果:为超级媒体应用程序确定了一种新的架构风格,Fielding 将其命名为 REST(Representational State Transfer)。Fielding 断言,使用且符合 REST 设计约束的 Web 上部署的组件可以充分利用 Web 的有用特性。他还警告说,违背 REST 准则的 Web 组件都将无法利用这些优点。

早期时,大部分 Web 站点和简单的 Web 应用程序实际上都是遵守 REST 准则的。但是随着融入式 Web 应用程序的日益普及,Web 应用程序架构逐渐开始背离 REST 准则了,此后因果循环,情况日益恶化。融入式服务器端 Web 架构的问题很难分析清楚,因为在使用这种架构风格的十年中,已经建立起这样一种信仰:这些问题都是 Web 应用程序架构所固有的。实际上,这并非是 Web 应用程序架构的问题。而是由服务器端 Web 应用程序架构风格所产生的问题。要打破这种偏见,我们来回顾一下整个架构是如何发展到现在这种状态的,这会很有帮助。我们将说明为什么在 Ajax 应用程序创建在商业上可行之后,过去接受的很多假设现在都不再成立了。





回页首


Web 应用程序的简史

Berners-Lee 创造了 Web,最初是将 Web 作为研究人员远程共享文档和在文档之间创建简单链接以加速知识和思想传播的一种手段。然而,URI 标准的架构特征很快实现了除静态文件之外更多内容的共享。

提供静态文档的 Web 站点

Web 上最早的内容由一些静态 HTML 文档组成,其中有很多到其他静态文档的链接,如图 1 所示:


图 1. 提供静态文档的 Web 站点
提供静态文档的 Web 站点

REST 使静态文档的检索极其高效、可伸缩,这是因为它们可以根据 URI 和最后修改日期来轻松缓存。很快开发人员就超越了静态文档的领域,开始动态文档的提供。

早期的动态 Web 应用程序

Berners-Lee 和其他人设计了 URI 标准,为资源的统一唯一标识提供支持,同时使其表示(HTML、文本等)根据 Web 客户机(通常是 Web 浏览器)和 Web 服务器之间的协商结果而变化。由于 URI 将资源标识和资源的底层存储机制区分开来,因此 Web 开发人员可以创建一些程序,使之检查 URI 语法,并动态生成文档,将预先定义的 UI 元素和动态检索的数据(通常是从关系数据库中)合并在一起,如图 2 所示。尽管这些文档是生成的,但是它们的缓存特征与静态文件的完全相同。


图 2. 以嵌入 HTML 模板代码形式提供数据库记录的 Web 站点
以嵌入 HTML 模板代码形式提供数据库记录的 Web 站点

此类早期应用程序的一个简单例子是统一目录 Web 应用程序。这种应用程序通常以如下方式工作:

  1. 用户在 Web 表单中输入名字(例如,Bill Higgins),并单击提交按钮。
  2. 表单根据输入的名字创建一个 URI,并从服务器上请求这个 URI 的内容(例如 GET http://psu.edu/Directory/Bill+Higgins)。
  3. 服务器检查这个 URI,并使用这个学生的电话号码和地址来生成一个 Web 页面。
  4. 服务器将所生成的页面发回到用户的浏览器上。

这种交互的一个重要特性是它是幂等的(idempotent),也就是说除非底层资源发生变化(例如 Bill 修改了自己的电话号码),否则同一请求的结果总是相同的。这意味着浏览器或代理服务器都可以在本地对 Bill Higgins 的文档进行缓存,只要底层资源没有发生变化,那就可以从本地缓存中检索资源,而不再需要从远程服务器检索。这种方法能提高用户感受到的响应性,并增加系统 整体效率和可伸缩性。这些早期的动态 Web 应用程序可以很好地工作,将大量的信息送至用户指尖。

融入式 Web 应用程序

下一代 Web 应用程序的目标就是高度融入,提供个性化的内容和富应用程序模型。在过去十年中,Web 开发人员成功创建了这些融入式应用程序。一个非常恰当的例子是 Amazon.com 电子商务站点。当用户与 Amazon Web 应用程序进行交互时,它会创建复杂的客户页面来推荐有针对性的商品,显示浏览历史记录,并显示用户购物车中商品的价格。





回页首


融入式服务器端应用程序和 REST

融入式 Web 应用程序确实非常有用,但服务器端的融入式 Web 应用程序风格从根本上来说是不符合 REST 架构准则的。具体来说,它违背了一项关键的 REST 约束,并且没有利用 REST 最为重要的一些优点,因此又产生了一组新问题。

违背了 “无状态服务器” 约束

REST 的 “客户机-无状态-服务器” 约束禁止在服务器上保存会话状态。符合这一约束进行设计可以提高系统的可见性、可靠性和可伸缩性。但是融入式服务器端 Web 应用程序希望能够为单个用户提供大量个性化内容,因此必须在两种设计之间作出选择。第一种设计要在每个客户机请求中都发送大量状态信息,因此每个请求都完 整地保留了上下文的内容,服务器是无状态的。第二种解决方案表面上来看比较简单,应用程序开发人员和中间件供应商都比较倾向于这种方法,它只是简单地发送 一个用户标识,并在服务器端为这个标识关联一个 “用户会话”(如图 3 所示)。第二种设计直接违背了客户机-无状态-服务器约束。尽管它确实可以实现我们想要的用户功能(具体来说就是指个性化),但却对这个架构进行了极大的 改动。


图 3. 融入式服务器端 Web 应用程序,其中包含了大量服务器端会话状态
融入式服务器端 Web 应用程序

Java™ Servlet 的 HttpSession API 正是一个此类变动的例子。HttpSession 让我们可以在状态和特定用户之间建立关联。这个 API 看起来对于开发新手非常简单。实际上,它似乎可以将任何对象保存到 HttpSession 中,并且不需要自己实现任何特定的查找逻辑就可以将这些对象取出来。但是当我们开始在 HttpSession 中放入更多对象时,就会开始注意到我们的应用服务器要占用的内存和处理资源越来越多。很快我们就确定自己需要将应用程序部署到集群环境中来应对日益增加的 资源需求。然后就会认识到,要让 HttpSession 在集群环境中工作,每个对象都必须实现 Java 的 Serializable 接口,以使会话数据能够在集群环境中的服务器间传递。然后必须确定应用服务器在关机/重启过程中是否要继续维护会话数据。很快您就会质疑,违背客户机-无状态-服务器约束是否真的是一个好主意。(实际上,很多开发人员都不了解这个约束。)

使分布式缓存变为不可能

融入式服务器端 Web 应用程序的第二个严重后果在于:它实际上不能利用 REST 的第一类支持进行资源缓存。引用 Fielding 的话来说,“添加缓存约束的优点是可以部分或完全避免某些交互操作,从而可以通过减少一系列交互的平均延迟来提高效率、可伸缩性以及用户可以感受到的性 能。不过这样做的代价是如果缓存中的陈旧数据与通过将请求直接发送给服务器而获得的数据有很大区别,那么缓存的可靠性就降低了。”

我们可以将融入式 Web 应用程序近似地看作一个活动实体,它会根据用户提供的新输入内容、其他人输入的新内容以及新的后台数据而不断发生变化。由于服务器必须根据多个用户与应用 程序的交互来生成每个页面,因此我们实际上无法两次生成相同的文档。因此,Web 浏览器或代理服务器无法缓存服务器资源。

有几种解决方案可以用来处理资源无法缓存的问题。一种就是创建细粒度的资源在服务器端的缓存,这样服务器就可以通过预先组合好的部分来构建一个粗粒 度的页面,而不是通过基本元素(HTML 和数据)从头开始一步步地构建这种页面了。但是问题依然存在:每个请求都会导致大量的服务器处理,这会损害系统的可伸缩性,还可能会对用户感受到的响应性 造成负面影响。

无法提供可缓存资源的另外一个结果是:动态程度相当高的 Web 应用程序必须显式地禁止搜索引擎和其他类型的 “机器人”作出请求,因为处理这类请求的成本都非常昂贵;而在符合 REST 准则的应用程序中,只需一次性地将某个资源提供给那一类 “机器人”,然后对它们的后续访问发送一条简单的 “Not-modified” 消息即可。

不使用 Ajax 的客户端的处理

随着访问 Web 应用程序的用户越来越多,系统需要的资源也会逐渐增加。可以让服务器来处理这一切,但将需要容量更大的服务器或集群服务器(服务器端状态在集群环境中并不 太适用)。但如果将处理分布到客户机上,那么每增加一名新用户,您就相当于有了一台支持部分新负载的新电脑。如果将会话状态分布到客户机上,那么就有了一 个无状态的服务器 —— 这是可伸缩 Web 应用程序中令人满意的一项特性。这看上去应该是种非常明智的做法,那么为什么不按这种方法设计所有融入式 Web 应用程序呢?在 Ajax 出现之前,答案非常简单:每次用户访问一个新的 Web 页面时,应用程序状态时就会被销毁。

每次访问一个 Web 页面时,都要下载一个或一组包含内容的文件(包含在结构化信息中的数据,例如表和列表),以及影响内容外观的样式(例如,红色文本)。在 Web 浏览器中,这些信息都是作为文档对象的抽象集来查看的。下面的列表为例:

  • Ford
  • BMW
  • Toyota

浏览器会认为这个 HTML 是一个 “无序清单” 对象,其中包含了 3 个列表元素;每个列表元素都包含文本。整个文档可视为一个复杂的相关对象树。当我们从一个页面浏览到另外一个页面时,浏览器就会销毁当前页面的对象树,并为下一页创建一个新的对象树。

但是为什么要在一个负载过重的服务器上集中这么多的资源消耗呢?从理论上来说,我们什么时候可以将处理和内存需求分布到客户机呢?简单的答案是给定传统 Web 浏览器约束,这是不可行的(请参看 不使用 Ajax 的客户端处理)。但是 Ajax 架构风格使开发人员可以将处理和状态需求分布到客户机。请继续阅读,学习为什么选择使用 Ajax 风格的融入式应用程序可以继续遵循 REST 准则,并充分利用它的优势。

Ajax 和 REST

正如我们前面看到的一样,传统的服务器端 Web 应用程序将数据的标识和服务器上的动态数据元素合并在了一起,并将所构成的完整 HTML 文档返回给浏览器。Ajax 应用程序在其主要 UI 和浏览器中的主要逻辑方面有所不同;基于浏览器的应用程序代码可以在必要时获取新的服务器数据,并将这些数据织入当前页面(请参看 参考资料 中 Jesse James Garrett 有关 Ajax 的启蒙文章)。呈现和数据绑定的位置看起来可能是一个实现细节,但是这种区别会导致完全不同的架构风格。

利用有状态 Web 客户机的优点

人们通常将 Ajax 应用程序描述成无需在每次点击时彻底地刷新整页的 Web 页面。尽管这个描述非常确切,但是根本的动机在于彻底刷新整页会令用户不耐烦,从而无法获得愉快、融入式的用户体验。从架构的角度来看,整个页面全部刷新 的设计甚至非常危险,这种设计使您无法选择在客户机存储应用程序状态,这可能会导致妨碍应用程序充分利用 Web 最强大的架构设计点的设计决策。

Ajax 让我们不需要进行完全刷新就可以与服务器进行交互,这一事实使有状态客户机再次成为可用选择。这一点对于动态融入式 Web 应用程序架构的可能性有深远的影响:由于应用程序资源和数据资源的绑定转换到了客户端,因此这些应用程序都可以享受这两个世界中最好的东西 —— 融入式 Web 应用程序中动态、个性化的用户体验,以及遵守 REST 准则的应用程序中简单、可伸缩的架构。

缓存 Ajax 引擎

设想一下,将 Amazon.com 彻底重新实现为一个 Ajax 应用程序 —— 一个 Web 页面可以从服务器上动态获取所有的数据。(出于商业原因,Amazon 可能并不希望这样做,不过那是其他文章讨论的话题了。)由于现在有很多 UI 和应用程序逻辑都可以在客户机而不是在服务器上运行,根据 Garrett 的说法,最初加载页面时需要下载 Amazon 的 Ajax “引擎”。这个引擎包含大量应用程序逻辑(以 JavaScript 代码实现),另外还有此后将使用从服务器上异步获取的数据填充的 UI 框架(见图 4):


图 4. 融入式 Ajax 应用程序
融入式 Ajax 应用程序

Ajax 引擎一个有趣的特征就是:尽管它包含了很多应用程序逻辑和表示框架元素,但是如果经过恰当的设计,它可以不包含任何业务数据或个性化内容。应用程序和表示 都冻结在部署时。在典型的 Web 环境中,应用程序资源可能 6 个月才会变更一次。这意味着负责隔离应用程序资源和数据资源的 Ajax 引擎是高度可缓存的。

Dojo Toolkit 就是一个很好的例子(请参看 参考资料)。 Dojo 提供了构建时工具来创建一个包含所有应用程序逻辑、表示和风格的压缩 JavaScript 文件。由于它终究只是一个文件,因此 Web 浏览器可以对其进行缓存,这意味着我们第二次访问启用 Dojo 的 Web 应用程序时,很可能就会从浏览器缓存中加载 Ajax,而不是从服务器上加载它。我们可以将这种情况与高度融入化的服务器端 Web 应用程序进行一下对比,后者每次请求都会导致大量的服务器处理,因为浏览器和网络中介不能对缓存不断变化的资源。

由于 Ajax 应用程序引擎只是一个文件,因此它也是可以使用代理缓存的。在大型的企业内部网中,只要有一名员工曾经下载过某个特定版本的应用程序的 Ajax 引擎,其他任何人都可以从内部网网关上上获取一个缓存过的拷贝。

因此对于应用程序资源来说,经过良好定义的 Ajax 应用程序引擎符合 REST 准则,与服务器端 Web 应用程序相比,它具有显著的可伸缩性优势。

缓存 Ajax 数据

用户浏览一个 Ajax Web 站点,加载 Ajax 应用程序引擎,最好是从浏览器缓存中加载的,否则就从本地代理服务器加载。那么对于业务数据来说情况如何呢?由于应用程序逻辑和状态都在浏览器上驻留并执 行,因此应用程序与服务器的交互就与传统 Web 应用程序的方式有很大的不同。不需要获取混合的内容页面,只需要获取业务数据即可。

现在回到 Amazon.com 的例子上来,假设我们点击了一个链接,要查看有关设计模式的一本书籍。在 Amazon.com 目前的应用程序中,链接点击操作会发送很多标识所请求的资源的信息。它还会发送很多会话状态信息,这让服务器可以创建一个新页面,其中可以包括之前的状态 (例如最近查看的内容)、个性化信息(例如您在 1999 年购买的书籍)以及实际的业务资源本身。应用程序是动态且高度个性化的 —— 但是却不能缓存,也无法伸缩(正如 Amazon 所示范的一样,这些架构问题都可以通过投入大量资金构建基础设施来克服)。现在我们考虑一下这个操作在(假想的)Ajax 版本的应用程序中的情况。对于 “最近查看的内容” 并不需要进行处理。当我们点击某个链接时,这些在页面上已经存在的信息并不会消失。有两个请求很可能会与设计模式的书籍有关:

  • /Books/0201633612(其中 0201633612 是设计模式书的 ISBN 号)
  • /PurchaseHistory/0201633612/bhiggins@us.ibm.com

第一个假定的请求会返回有关书籍的信息(作者、标题、简介等);其中并没有包含特定于用户的数据。特定于用户的数据意味着当更多用户请求相同的资源 时,很可能会从 Internet 上的中间节点上来检索缓存版本,而不是从原始服务器上检索这些资源。这种特性会降低服务器和总体网络负载。另外一方面,第二个请求包含了特定于用户的信息 (Bill Higgins 的购买该书的历史记录)。由于这些数据包括一些个性化信息,因此只有一名用户需要从这个 URI 中获取并缓存数据。尽管这种个性化数据并没有非个性化数据的可伸缩特性,但是重要的问题是这些信息都是直接从 URL 中获取的,因此都具有这样的正面特征:它们都不会妨碍其他可缓存的应用程序和数据资源的缓存

Ajax 和健壮性

Ajax 架构风格的另外一个优点是它可以轻松处理服务器的故障。正如我们前面介绍的一样,具有融入式用户体验的服务器端 Web 应用程序通常会在服务器上保存大量的用户会话状态。如果服务器发生了故障,会话状态就丢失了,那么用户就会体验到非常奇怪的浏览器行为(“为什么我又回到 主页上来了?我的购物车中的东西都到哪里去了?”)。在采用有状态客户机和无状态服务的 Ajax 应用程序中,服务器崩溃/重新启动对于用户来说都是完全透明的,因为服务器崩溃不会影响会话状态,这些都保存在用户的浏览器中;无状态服务的行为是幂等 的,可以由用户请求的内容来单独确定。

[转]JUnit4教程

引言

毋庸置疑,程 序员要对自己编写的代码负责,您不仅要保证它能通过编译,正常地运行,而且要满足需求和设计预期的效果。单元测试正是验证代码行为是否满足预期的有效手段 之一。但不可否认,做测试是件很枯燥无趣的事情,而一遍又一遍的测试则更是让人生畏的工作。幸运的是,单元测试工具 JUnit 使这一切变得简单艺术起来。

JUnit 是 Java 社区中知名度最高的单元测试工具。它诞生于 1997 年,由 Erich Gamma 和 Kent Beck 共同开发完成。其中 Erich Gamma 是经典著作《设计模式:可复用面向对象软件的基础》一书的作者之一,并在 Eclipse 中有很大的贡献;Kent Beck 则是一位极限编程(XP)方面的专家和先驱。

麻雀虽小,五脏俱全。JUnit 设计的非常小巧,但是功能却非常强大。Martin Fowler 如此评价 JUnit:在软件开发领域,从来就没有如此少的代码起到了如此重要的作用。它大大简化了开发人员执行单元测试的难度,特别是 JUnit 4 使用 Java 5 中的注解(annotation)使测试变得更加简单。





回页首


JUnit 4 初体验

在开始体验 JUnit 4 之前,我们需要以下软件的支持:

  • Eclipse:最为流行的 IDE,它全面集成了 JUnit,并从版本 3.2 开始支持 JUnit 4。当然 JUnit 并不依赖于任何 IDE。您可以从 http://www.eclipse.org/ 上下载最新的 Eclipse 版本。
  • Ant:基于 Java 的开源构建工具,您可以在 http://ant.apache.org/ 上得到最新的版本和丰富的文档。Eclipse 中已经集成了 Ant,但是在撰写本文时,Eclipse 使用的 Ant 版本较低(必需 1.7 或者以上版本),不能很好的支持 JUnit 4。
  • JUnit:它的官方网站是 http://www.junit.org/。您可以从上面获取关于 JUnit 的最新消息。如果您和本文一样在 Eclipse 中使用 JUnit,就不必再下载了。

首先为我们的体验新建一个 Java 工程 ―― coolJUnit。现在需要做的是,打开项目 coolJUnit 的属性页 -> 选择"Java Build Path"子选项 -> 点选"Add Library…"按钮 -> 在弹出的"Add Library"对话框中选择 JUnit(图1),并在下一页中选择版本 4.1 后点击"Finish"按钮。这样便把 JUnit 引入到当前项目库中了。


图1 为项目添加 JUnit 库
图1 为项目添加 JUnit 库
请注意 JDK 的版本

JUnit 4.1 是基于 Java 5 的升级版本,它使用了 Tiger 中的很多新特性来简化原有的使用方式。正因为如此,它并不能直接运行在 JDK1.4.x 版本上。如果您需要在 JDK1.4.x 版本使用 JUnit 的话,请使用 3.8.1 版本。

可 以开始编写单元测试了吗?等等……,您打算把单元测试代码放在什么地方呢?把它和被测试代码混在一起,这显然会照成混乱,因为单元测试代码是不会出现在最 终产品中的。建议您分别为单元测试代码与被测试代码创建单独的目录,并保证测试代码和被测试代码使用相同的包名。这样既保证了代码的分离,同时还保证了查 找的方便。遵照这条原则,我们在项目 coolJUnit 根目录下添加一个新目录 testsrc,并把它加入到项目源代码目录中(加入方式见 图2)。


图2 修改项目源代码目录
图2 修改项目源代码目录

现在我们得到了一条 JUnit 的最佳实践:单元测试代码和被测试代码使用一样的包,不同的目录。

一 切准备就绪,一起开始体验如何使用 JUnit 进行单元测试吧。下面的例子来自笔者的开发实践:工具类 WordDealUtil 中的静态方法 wordFormat4DB 是专用于处理 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();
}
}

它是否能按照预期的效果执行呢?尝试为它编写 JUnit 单元测试代码如下:

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);
}
}

很普通的一个类嘛!测试类 TestWordDealUtil 之所以使用"Test"开头,完全是为了更好的区分测试类与被测试类。测试方法 wordFormat4DBNormal 调用执行被测试方法 WordDealUtil.wordFormat4DB,以判断运行结果是否达到设计预期的效果。需要注意的是,测试方法 wordFormat4DBNormal 需要按照一定的规范书写:

  1. 测试方法必须使用注解 org.junit.Test 修饰。
  2. 测试方法必须使用 public void 修饰,而且不能带有任何参数。

测 试方法中要处理的字符串为"employeeInfo",按照设计目的,处理后的结果应该为"employee_info"。assertEquals 是由 JUnit 提供的一系列判断测试结果是否正确的静态断言方法(位于类 org.junit.Assert 中)之一,我们使用它将执行结果 result 和预期值"employee_info"进行比较,来判断测试是否成功。

看看运行结果如何。在测试类上点击右键,在弹出菜单中选择 Run As JUnit Test。运行结果如下图所示:


图3 JUnit 运行成功界面
图3 JUnit 运行成功界面

绿 色的进度条提示我们,测试运行通过了。但现在就宣布代码通过了单元测试还为时过早。记住:您的单元测试代码不是用来证明您是对的,而是为了证明您没有错。 因此单元测试的范围要全面,比如对边界值、正常值、错误值得测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。显然, 我们的测试才刚刚开始,继续补充一些对特殊情况的测试:

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);
}
}

再次运行测试。很遗憾,JUnit 运行界面提示我们有两个测试情况未通过测试(图4)――当首字母大写时得到的处理结果与预期的有偏差,造成测试失败(failure);而当测试对 null 的处理结果时,则直接抛出了异常――测试错误(error)。显然,被测试代码中并没有对首字母大写和 null 这两种特殊情况进行处理,修改如下:

//修改后的方法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();
}


图4 JUnit 运行失败界面
图4 JUnit 运行失败界面

JUnit 将测试失败的情况分为两种:failure 和 error。Failure 一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;而 error 则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),也可能是被测试代码中的 一个隐藏的bug。

请牢记!

请牢记这一条 JUnit 最佳实践:测试任何可能的错误。单元测试不是用来证明您是对的,而是为了证明您没有错。

啊哈,再次运行测试,绿条又重现眼前。通过对 WordDealUtil.wordFormat4DB 比较全面的单元测试,现在的代码已经比较稳定,可以作为 API 的一部分提供给其它模块使用了。

不知不觉中我们已经使用 JUnit 漂亮的完成了一次单元测试。可以体会到 JUnit 是多么轻量级,多么简单,根本不需要花心思去研究,这就可以把更多的注意力放在更有意义的事情上――编写完整全面的单元测试。





回页首


JUnit 深入

当然,JUnit 提供的功能决不仅仅如此简单,在接下来的内容中,我们会看到 JUnit 中很多有用的特性,掌握它们对您灵活的编写单元测试代码非常有帮助。

Fixture

何 谓 Fixture?它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。在编写单元测试的过程中,您会发现在大 部分的测试方法在进行真正的测试之前都需要做大量的铺垫――为设计准备 Fixture 而忙碌。这些铺垫过程占据的代码往往比真正测试的代码多得多,而且这个比率随着测试的复杂程度的增加而递增。当多个测试方法都需要做同样的铺垫时,重复代 码的"坏味道"便在测试代码中弥漫开来。这股"坏味道"会弄脏您的代码,还会因为疏忽造成错误,应该使用一些手段来根除它。

JUnit 专门提供了设置公共 Fixture 的方法,同一测试类中的所有测试方法都可以共用它来初始化 Fixture 和注销 Fixture。和编写 JUnit 测试方法一样,公共 Fixture 的设置也很简单,您只需要:

  1. 使用注解 org,junit.Before 修饰用于初始化 Fixture 的方法。
  2. 使用注解 org.junit.After 修饰用于注销 Fixture 的方法。
  3. 保证这两种方法都使用 public void 修饰,而且不能带有任何参数。

遵循上面的三条原则,编写出的代码大体是这个样子:

//初始化Fixture方法
@Before public void init(){……}

//注销Fixture方法
@After public void destroy(){……}

这样,在每一个测试方法执行之前,JUnit 会保证 init 方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit 又会调用 destroy 方法注销测试环境。注意是每一个测试方法的执行都会触发对公共 Fixture 的设置,也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的(图5)。这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。


图5 方法级别 Fixture 执行示意图
图5 方法级别 Fixture 执行示意图

可 是,这种 Fixture 设置方式还是引来了批评,因为它效率低下,特别是在设置 Fixture 非常耗时的情况下(例如设置数据库链接)。而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个 测试方法重新设置一次 Fixture。因此在 JUnit 4 中引入了类级别的 Fixture 设置方法,编写规范如下:

  1. 使用注解 org,junit.BeforeClass 修饰用于初始化 Fixture 的方法。
  2. 使用注解 org.junit.AfterClass 修饰用于注销 Fixture 的方法。
  3. 保证这两种方法都使用 public static void 修饰,而且不能带有任何参数。

类级别的 Fixture 仅会在测试类中所有测试方法执行之前执行初始化,并在全部测试方法测试完毕之后执行注销方法(图6)。代码范本如下:

//类级别Fixture初始化方法
@BeforeClass public static void dbInit(){……}

//类级别Fixture注销方法
@AfterClass public static void dbClose(){……}


图6 类级别 Fixture 执行示意图
图6 类级别 Fixture 执行示意图

异常以及时间测试

注 解 org.junit.Test 中有两个非常有用的参数:expected 和 timeout。参数 expected 代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,则 JUnit 会认为这个测试没有通过。这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。举例来说,方法 supportDBChecker 用于检查用户使用的数据库版本是否在系统的支持的范围之内,如果用户使用了不被支持的数据库版本,则会抛出运行时异常 UnsupportedDBVersionException。测试方法 supportDBChecker 在数据库版本不支持时是否会抛出指定异常的单元测试方法大体如下:

@Test(expected=UnsupportedDBVersionException.class)
public void unsupportedDBCheck(){
……
}

注解 org.junit.Test 的另一个参数 timeout,指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,则JUnit认为测试失败。这个参数对于性能 测试有一定的帮助。例如,如果解析一份自定义的 XML 文档花费了多于 1 秒的时间,就需要重新考虑 XML 结构的设计,那单元测试方法可以这样来写:

@Test(timeout=1000)
public void selfXMLReader(){
……
}

忽略测试方法

JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行。例如下面的代码便表示由于没有了数据库链接,提示 JUnit 忽略测试方法 unsupportedDBCheck:

@ Ignore("db is down")
@Test(expected=UnsupportedDBVersionException.class)
public void unsupportedDBCheck(){
……
}

但是一定要小心。注解 org.junit.Ignore 只能用于暂时的忽略测试,如果需要永远忽略这些测试,一定要确认被测试代码不再需要这些测试方法,以免忽略必要的测试点。

测试运行器

又 一个新概念出现了――测试运行器,JUnit 中所有的测试方法都是由它负责执行的。JUnit 为单元测试提供了默认的测试运行器,但 JUnit 并没有限制您必须使用默认的运行器。相反,您不仅可以定制自己的运行器(所有的运行器都继承自 org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。指定方法也很简单,使用注解 org.junit.runner.RunWith 在测试类上显式的声明要使用的运行器即可:

@RunWith(CustomTestRunner.class)
public class TestWordDealUtil {
……
}

显而易见,如果测试类没有显式的声明使用哪一个测试运行 器,JUnit 会启动默认的测试运行器执行测试类(比如上面提及的单元测试代码)。一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用 JUnit 提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制 JUnit 测试方式时,显式的声明测试运行器就必不可少了。

测试套件

在 实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在我们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。为了解决 这个问题,JUnit 提供了一种批量运行测试类的方法,叫做测试套件。这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。测试套件的写法非常简单,您只 需要遵循以下规则:

  1. 创建一个空类作为测试套件的入口。
  2. 使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuiteClasses 修饰这个空类。
  3. 将 org.junit.runners.Suite 作为参数传入注解 RunWith,以提示 JUnit 为此类使用套件运行器执行。
  4. 将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。
  5. 保证这个空类使用 public 修饰,而且存在公开的不带有任何参数的构造函数。
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 {
}

上例代码中,我们将前文提到的测试类 TestWordDealUtil 放入了测试套件 RunAllUtilTestsSuite 中,在 Eclipse 中运行测试套件,可以看到测试类 TestWordDealUtil 被调用执行了。测试套件中不仅可以包含基本的测试类,而且可以包含其它的测试套件,这样可以很方便的分层管理不同模块的单元测试代码。但是,您一定要保证 测试套件之间没有循环包含关系,否则无尽的循环就会出现在您的面前……。

参数化测试

回顾一下我们在小节"JUnit 初体验" 中举的实例。为了保证单元测试的严谨性,我们模拟了不同类型的字符串来测试方法的处理能力,为此我们编写大量的单元测试方法。可是这些测试方法都是大同小 异:代码结构都是相同的,不同的仅仅是测试数据和期望值。有没有更好的方法将测试方法中相同的代码结构提取出来,提高代码的重用度,减少复制粘贴代码的烦 恼?在以前的 JUnit 版本上,并没有好的解决方法,而现在您可以使用 JUnit 提供的参数化测试方式应对这个问题。

参数化测试的编写稍微有点麻烦(当然这是相对于 JUnit 中其它特性而言):

  1. 为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。
  2. 为测试类声明几个变量,分别用于存放期望值和测试所用数据。
  3. 为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。
  4. 为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
  5. 编写测试方法,使用定义的变量作为参数进行测试。

我们按照这个标准,重新改造一番我们的单元测试代码:

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 时的处理情况
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
{"", ""}, //测试空字符串时的处理情况
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
{"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));
}
}

很明显,代码瘦身了。在静态方法 words 中,我们使用二维数组来构建测试所需要的参数列表,其中每个数组中的元素的放置顺序并没有什么要求,只要和构造函数中的顺序保持一致就可以了。现在如果再 增加一种测试情况,只需要在静态方法 words 中添加相应的数组即可,不再需要复制粘贴出一个新的方法出来了。





回页首


JUnit 和 Ant

随 着项目的进展,项目的规模在不断的膨胀,为了保证项目的质量,有计划的执行全面的单元测试是非常有必要的。但单靠JUnit提供的测试套件很难胜任这项工 作,因为项目中单元测试类的个数在不停的增加,测试套件却无法动态的识别新加入的单元测试类,需要手动修改测试套件,这是一个很容易遗忘得步骤,稍有疏忽 就会影响全面单元测试的覆盖率。

当然解决的方法有多种多样,其中将 JUnit 与构建利器 Ant 结合使用可以很简单的解决这个问题。Ant ―― 备受赞誉的 Java 构建工具。它凭借出色的易用性、平台无关性以及对项目自动测试和自动部署的支持,成为众多项目构建过程中不可或缺的独立工具,并已经成为事实上的标准。 Ant 内置了对 JUnit 的支持,它提供了两个 Task:junit 和 junitreport,分别用于执行 JUnit 单元测试和生成测试结果报告。使用这两个 Task 编写构建脚本,可以很简单的完成每次全面单元测试的任务。

不过,在使用 Ant 运行 JUnit 之前,您需要稍作一些配置。打开 Eclipse 首选项界面,选择 Ant -> Runtime 首选项(见图7), 将 JUnit 4.1 的 JAR 文件添加到 Classpath Tab 页中的 Global Entries 设置项里。记得检查一下 Ant Home Entries 设置项中的 Ant 版本是否在 1.7.0 之上,如果不是请替换为最新版本的 Ant JAR 文件。


图7 Ant Runtime 首选项
图7 Ant Runtime 首选项

剩下的工作就是要编写 Ant 构建脚本 build.xml。虽然这个过程稍嫌繁琐,但这是一件一劳永逸的事情。现在我们就把前面编写的测试用例都放置到 Ant 构建脚本中执行,为项目 coolJUnit 的构建脚本添加一下内容:

<?xml version="1.0"?>
<!-- =============================================
auto unittest task
ai92
========================================== -->
<project name="auto unittest task" default="junit and report" basedir=".">

<property name="output folder" value="bin"/>

<property name="src folder" value="src"/>

<property name="test folder" value="testsrc"/>

<property name="report folder" value="report" />

<!-- - - - - - - - - - - - - - - - - -
target: test report folder init
- - - - - - - - - - - - - - - - - -->
<target name="test init">
<mkdir dir="${report folder}"/>
</target>

<!-- - - - - - - - - - - - - - - - - -
target: compile
- - - - - - - - - - - - - - - - - -->
<target name="compile">
<javac srcdir="${src folder}" destdir="${output folder}" />
<echo>compilation complete!</echo>
</target>

<!-- - - - - - - - - - - - - - - - - -
target: compile test cases
- - - - - - - - - - - - - - - - - -->
<target name="test compile" depends="test init">
<javac srcdir="${test folder}" destdir="${output folder}" />
<echo>test compilation complete!</echo>
</target>

<target name="all compile" depends="compile, test compile">
</target>

<!-- ========================================
target: auto test all test case and output report file
===================================== -->
<target name="junit and report" depends="all compile">
<junit printsummary="on" fork="true" showoutput="true">
<classpath>
<fileset dir="lib" includes="**/*.jar"/>
<pathelement path="${output folder}"/>
</classpath>
<formatter type="xml" />
<batchtest todir="${report folder}">
<fileset dir="${output folder}">
<include name="**/Test*.*" />
</fileset>
</batchtest>
</junit>
<junitreport todir="${report folder}">
<fileset dir="${report folder}">
<include name="TEST-*.xml" />
</fileset>
<report format="frames" todir="${report folder}" />
</junitreport>
</target>
</project>

Target junit report 是 Ant 构建脚本中的核心内容,其它 target 都是为它的执行提供前期服务。Task junit 会寻找输出目录下所有命名以"Test"开头的 class 文件,并执行它们。紧接着 Task junitreport 会将执行结果生成 HTML 格式的测试报告(图8)放置在"report folder"下。

为整个项目的单元测试类确定一种命名风格。不仅是出于区分类别的考虑,这为 Ant 批量执行单元测试也非常有帮助,比如前面例子中的测试类都已"Test"打头,而测试套件则以"Suite"结尾等等。


图8 junitreport 生成的测试报告
图8 junitreport 生成的测试报告

现在执行一次全面的单元测试变得非常简单了,只需要运行一下 Ant 构建脚本,就可以走完所有流程,并能得到一份详尽的测试报告。您可以在 Ant 在线手册 中获得上面提及的每一个 Ant 内置 task 的使用细节。

[转]Apache commons 简介

Apache Commons是一个非常有用的工具包,解决各种实际的通用问题,下面是一个简述表,详细信息访问http://commons.apache.org/

BeanUtils

Commons-BeanUtils 提供对 Java 反射和自省API的包装

Betwixt

Betwixt提供将 JavaBean 映射至 XML 文档,以及相反映射的服务.

Chain

Chain 提供实现组织复杂的处理流程的"责任链模式".

CLI

CLI 提供针对命令行参数,选项,选项组,强制选项等的简单API.

Codec

Codec 包含一些通用的编码解码算法。包括一些语音编码器, Hex, Base64, 以及URL encoder.

Collections

Commons-Collections 提供一个类包来扩展和增加标准的 Java Collection框架

Configuration

Commons-Configuration 工具对各种各式的配置和参考文件提供读取帮助.

Daemon

一种 unix-daemon-like java 代码的替代机制

DBCP

Commons-DBCP 提供数据库连接池服务

DbUtils

DbUtils 是一个 JDBC helper 类库,完成数据库任务的简单的资源清除代码.

Digester

Commons-Digester 是一个 XML-Java对象的映射工具,用于解析 XML配置文件.

Discovery

Commons-Discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。.

EL

Commons-EL 提供在JSP2.0规范中定义的EL表达式的解释器.

FileUpload

FileUpload 使得在你可以在应用和Servlet中容易的加入强大和高性能的文件上传能力

HttpClient

Commons-HttpClient 提供了可以工作于HTTP协议客户端的一个框架.

IO

IO 是一个 I/O 工具集

Jelly

Jelly是一个基于 XML 的脚本和处理引擎。 Jelly 借鉴了 JSP 定指标签,Velocity, Cocoon和Xdoclet中的脚本引擎的许多优点。Jelly 可以用在命令行, Ant 或者 Servlet之中。

Jexl

Jexl是一个表达式语言,通过借鉴来自于Velocity的经验扩展了JSTL定义的表达式语言。.

JXPath

Commons-JXPath 提供了使用Xpath语法操纵符合Java类命名规范的 JavaBeans的工具。也支持 maps, DOM 和其他对象模型。.

Lang

Commons-Lang 提供了许多许多通用的工具类集,提供了一些java.lang中类的扩展功能

Latka

Commons-Latka 是一个HTTP 功能测试包,用于自动化的QA,验收和衰减测试.

Launcher

Launcher 组件是一个交叉平台的Java 应用载入器。 Commons-launcher 消除了需要批处理或者Shell脚本来载入Java 类。.原始的 Java 类来自于Jakarta Tomcat 4.0 项目

Logging

Commons-Logging 是一个各种 logging API实现的包裹类.

Math

Math 是一个轻量的,自包含的数学和统计组件,解决了许多非常通用但没有及时出现在Java标准语言中的实践问题.

Modeler

Commons-Modeler 提供了建模兼容JMX规范的 Mbean的机制.

Net

Net 是一个网络工具集,基于 NetComponents 代码,包括 FTP 客户端等等。

Pool

Commons-Pool 提供了通用对象池接口,一个用于创建模块化对象池的工具包,以及通常的对象池实现.

Primitives

Commons-Primitives提供了一个更小,更快和更易使用的对Java基本类型的支持。当前主要是针对基本类型的 collection。.

Validator

The commons-validator提供了一个简单的,可扩展的框架来在一个XML文件中定义校验器 (校验方法)和校验规则。支持校验规则的和错误消息的国际化。