内容:
1 相关开源或第三方技术
1.1 ant 项目构建工具
1.2 junit单元测试
1.3 cactus单元测试
1.4 clover测试覆盖率计算
1.5 statcvs项目度量工具
1.6 velocity模版系统
2 文档书写辅助工具
参考资料
关于作者
本文是实战每晚构建系列的第二篇,主要叙述在设计构建平台时要考虑的一些开源或第三方技术,其中既有有类似于"Hello
world"的入门介绍,也有精髓内容解析,还有注意点提醒。
1、相关开源或第三方技术
在进行设计之前,我们有必要了解一些开源或第三方在项目构建方面的技术。学习这些技术的最好方式是弄到一份,仔细阅读文档,实践一些小的例子,在工作当中使用之。
1.1 ant 项目构建工具
为了让大家更好地了解后面的设计,本节出了介绍基本知识外,还介绍了这个工具的主要特点中的三点:多个文件组成配置文件,目标依赖性,扩展,另外讲述了ant配置脚本的面向对象特性。
简述
Ant是Apache开源运动中的一份子,它是和大家所熟悉的Make系统一样的基于Java的构建工具。他克服了Make的os依赖性,同样也可以调用os特有的指令。不像Make使用操作系统的脚本命令,ant使用java
类来扩展自身。Ant的配置文件是流行的xml格式。
下面是一个最简单的build.xml文件:
<?xml version="1.0"
encoding="ISO-8859-1"?> <project name="projectTemplate"
default="init" basedir="."> <target
name="init" > <property name="lib.dir"
value="lib"/> <echo message="Hello
${user.name}! lib.dir is set to ${lib.dir}" > </echo>
</target> </project> |
运行ant命令将产生下面的结果:
gongys$ ant
gongys$ Hello gongys! lib.dir is set to lib |
在这个简单的build.xml显示了ant配置文件定义目标(target),定义属性(property),访问属性的方法,其中${user.name}是个系统属性。
多个xml文件定义ant配置文件
下面我们给出一个相对复杂的build.xml文件:
<?xml version="1.0"
encoding="ISO-8859-1"?> <!DOCTYPE project
[ <!ENTITY build-abstract SYSTEM "file:./build-abstract.xml">
]> <project name="projectTemplate" default="init"
basedir="."> <target name="init"
depends="init.variables"> <property name="lib.dir"
value="lib"/> <echo message="Hello
${user.name}! lib.dir is set to ${lib.dir}" />
<echo message="build.dir is set to ${build.dir} in build-abstract.xml
" > </echo> </target> <target
name=" clean" depends="init" > <del
dir="${build.dir}"/> </target>
&build-abstract; </project> |
其中<!ENTITY build-abstract SYSTEM "file:./build-abstract.xml">定义了一个名为build-abstract的实体,其内容为当前目录下的build-abstract.xml文件。&build-abstract;引用了这个实体,这样在build.xml文件就可以用build-abstract.xml定义的目标啦。
下面是build-abstract.xml的内容:
<target name="init.variables">
<property name="build.dir" value="tempbuild"/>
</target> |
开发和定义自己的task
ant是一个可以扩充的构建工具,开发者可以开发自己的java类来扩充ant。下面是一个简单的扩充类:
package
com.mydomain;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class MyVeryOwnTask extends Task {
private String msg;
// The method executing the task
public void execute() throws BuildException {
System.out.println(msg);
}
// The setter for the "message"
attribute
public void setMessage(String msg) {
this.msg = msg;
}
} |
这个扩展任务将有一个属性message,ant在执行这个任务时会调用execute方法。下面是在build.xml配置文件中使用这个扩展的示例:
<?xml version="1.0"?>
<project name="OwnTaskExample" default="main"
basedir="."> <taskdef name="mytask"
classname="com.mydomain.MyVeryOwnTask"> <classpath>
<pathelement location="where/u/put/the/class/"/>
</classpath> <target name="main">
<mytask message="Hello World! MyVeryOwnTask works!"/>
</target> </project> |
目标依赖性
了解ant的另外一点是target的依赖性,上面这个比较复杂一点的build.xml的依赖性如下图所示:
这样的依赖图使得执行命令ant init 时先执行init.variable目标中的指令,执行clean目标时先执行依次执行init.variables和init目标。
到目前为止,还没有哪一个集成工具开发出自动分析ant配置文件依赖性图的插件,但是命令行下已经有了。
这个工具名叫vizant,也就是一个实现了扩展ant任务的jar文件,还包含了一些文档和例子,下面是我产生上面的目标依赖图的build.xml
<?xml
version="1.0"?>
<!-- $Id: build.xml,v 1.1 2003/04/29 10:25:12 gongys Exp
$ -->
<project name="Vizant" basedir="."
default="dot">
<property name="build" location="output"/>
<property name="vizant.antfile" value="${buildfile}"/>
<property name="dot.format" value="png"/>
<target name="init">
<echo message="${vizant.antfile}" />
<tstamp/>
<mkdir dir="${build}"/>
</target>
<target name="defvizant">
<taskdef name="vizant" classname="net.sourceforge.vizant.Vizant"
classpath="vizant.jar"/>
</target>
<target name="vizant" depends="defvizant,init">
<vizant antfile="${vizant.antfile}" outfile="${build}/build.dot"
uniqueref="true"/>
</target>
<target name="dot" depends="vizant">
<exec executable="${basedir}/dot.exe" v
<arg line="-T${dot.format} ${build}/build.dot -o ${build}/out.${dot.format}"/>
</exec>
</target>
</project> |
你在要分析的项目目录下执行如下命令便可在output/out.png的依赖图形文件。
gongys$ ant -f vizant/build.xml -Dbuildfile=build.xml
-f vizant/build.xml定义了ant配置文件,-Dbuildfile=build.xml定义了要分析的ant配置文件。
Ant配置脚本的面向对象性
从上面可以知道一个ant的配置脚本可以由多个配置文件组成,一个配置文件由目标和属性定义语句组成。我们可以把属性看成是面向对象中的成员变量,目标看成是方法,这样一个配置文件就定义了一个"类",而且它的成员都是静态的,就是说不需要生成"对象"。一个类是可以运行的如果它的配置文件的顶级元素是<project>,这就好像我们的java类实现了public
static void main(String[] args)方法一样。可以用xml中的定义和引用实体的方式来申明一个"类"继承了另一个"类",这样我们可以实现面向对象当中的"类继承层次图";我们可以用<ant>任务来实现跨对象之间的调用(要求这些对象的类是可以运行的),这样就形成了"对象协作图";我们可以用<antcall>和目标的depends属性来实现对象内部的"方法调用"。
注意Ant配置脚本的面向对象模型没办法实现方法重载或覆盖。
1.2 junit单元测试
大部分集成工具都集成了junit单元测试插件,并有向导帮助写单元测试。Junit发行包的文档很详细地介绍了Junit的设计概念和所使用的设计模式。在这里我简单地说明如何写测试用例、在ant配置文件中调用测试用例和产生测试报告的方法。
写测试用例
下面是在eclipse junit向导对MyCode类编写的测试用例TestMyCode文件基础上写的代码:
import
junit.framework.TestCase;
/*
* Created on 2003-4-30
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code
and Comments
*/
/**
* @author gongys
*
* To change the template for this generated type comment go
to
* Window>Preferences>Java>Code Generation>Code
and Comments
*/
public class TestMyCode extends TestCase {
MyCode myFixture=null;
/**
* Constructor for TestTest.
* @param arg0
*/
public TestTest(String arg0) {
super(arg0);
}
/*
* @see TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
myFixture = new MyCode();
System.out.println("setup");
}
/*
* @see TestCase#tearDown()
*/
protected void tearDown() throws Exception {
super.tearDown();
myFixture = null;
System.out.println("teardown");
}
public void testSetName() {
myFixture.setName("gongys")
assertEquals("gongys", myFixture.getName());
System.out.println("testSetName");
System.out.println(this.getName());
}
public void testSetAge() {
System.out.println("testSetAge");
myFixture.setAge (12)
assertEquals(12,myFixture.getAge());
System.out.println(this.getName());
}
} |
有几点需要特殊指出:
一个TestCase子类中可以包含多个test方法,test方法的原型必须是public void
testXXX();
在执行过程中,junit框架为每一个test方法实例化一个TestCase子类;
执行testCase的顺序如下:setUp(),testXXX(),teardown();
fixture是指为每一个测试方法准备的东西:比如数据库连接,此时的目标等,一般在setUp()中设置,testXXX()中使用,teardown()中释放。
运行这个测试的结果如下:
setup
testSetName
testSetName
teardown
setup
testSetAge
testSetAge
teardown |
ant使用测试用例
利用ant 的junit任务和其子任务test可以在ant配置文件中执行单元测试,如下所示:
<target name="outter_unittest" depends="init">
<junit printsummary="yes" fork="yes"
haltonfailure="no" >
<classpath>
<fileset dir="${build.dir}">
<include name="TestMyCode.class" />
<include name="MyCode.class" />
</fileset>
<pathelement location="${lib.dir}/${junit.jar}"/>
</classpath>
<formatter type="xml"/>>
<!--this specify the output format of junit -->
<test name="TestMyCode" todir="tempjunit"
/>>
<!--this will run all testXXX methods of the TestMyCode
and generate the output to dir tempjunit , the output file
is TEST-TestMyCode .xml -->
</junit>
</target> |
需要注意的是:
要正确设置junit任务的classpath子元素,classpath至少要包含三样东西,TestCase子类比如TestMyCode,你测试的代码的java类比如MyCode,和junit.jar;
可以使用formatter子元素设置junit任务中test任务的输出的格式;
test任务可以设置输出文件的名字和目录;
junit任务还有一个子任务batchtest可以用通配符来指定TestCase子类。
Ant中生成测试报告
在上面的一节中我们谈到junit任务可以生成测试结果,并输出到指定的文件和目录中,在ant中,我们还可以用junitreport任务对这些测试结果进行处理,生成html文件:
<junitreport todir="./tempjunit
"> <fileset dir="./tempjunit ">
<include name="TEST-*.xml"/> </fileset>
<report format="frames" todir="./report/html"/>
</junitreport> |
junitreport任务首先把fileset中指定的测试结果归集成一个xml文件,接着用子任务report转化成html文件,子任务report的format属性指定生成的结果是框架的还是没框架的。
1.3 cactus单元测试
cactus单元测试工具是对junit框架的扩充,使junit的思想和便利同样用于Browser/Server web应用程序中的测试,具体的来说就是测试servlet,jsp和filter。
本节讲述cactus 单元测试原理,servlet测试用例的书写(jsp,filter的测试用例的书写请参照cactus文档),如何配置ant运行这样的测试。
cactus 单元测试原理
Cactus提供了好几个扩展JUnit Testcase的子类和相应的redirector,上面的工作原理图解释了cactus测试的工作原理。
其中YYYTestCase = ( ServletTestCase子类 | FilterTestCase子类
| JspTestCase 子类)
XXX我们写的testcase名字的后半部分。
下面我们分步骤解释在我们的cactus Testcase子类里头的每一个testXXX()方法的具体情况:
- JUnit 测试运行器调用YYYTestCase.runTest()方法。
- 这个方法寻找 beginXXX(WebRequest)方法,如果找到则执行。
- 传给beginXXX(WebRequest)方法的参数WebRequest 可用来设置
HTTP头, HTTP 参数,这些参数将被发送到第2步的 Redirector 代理。
- YYYTestCase.runTest() 方法打开连向Redirector 代理的HTTP
连接,beginXXX(WebRequest)方法设置的HTTP协议参数将被送到代理。
- Redirector 代理在服务端作为YYYTestCase的代理(其实我们的YYYTestCase被实例化两次,一次在客户端被JUnit
测试运行器实例化,一次在服务器端被代理实例化,客户端实例执行beginXXX() and endXXX()方法,服务端实例执行Junit
测试用例的方法setup(),testXXX(),and teardown())。
- Redirector 代理有下列事情可做:
- 用java的内省功能创建服务端实例;
- 设置一些缺省对象;
- 按照客户端实例的意愿创建session。
- 执行Junit 测试用例的方法setup(),testXXX(),and teardown();
- 我们的 testXXX()方法调用服务端代码来进行测试,使用assertEquals()方法对测试结果和预期结果进行比较,如果两者相符为测试成功,否则为测试失败;
- 如果测试失败,Redirector 代理将捕获testXXX()方法抛出的的异常;
- Redirector 代理将异常信息返回给客户端的JUnit 测试运行器,JUnit 测试运行器可以生成测试报告;
- 如果没有异常出现, YYYTestCase.runTest()方法寻找
- endXXX(org.apache.cactus.WebResponse) endXXX(com.meterware.httpunit.WebResponse)
(后者用在和httpunit集成中) 方法,如果找到则执行。
- endXXX方法中,我们可以检查返回的HTTP 头, Cookies 和output stream
,这个检查可以借助于Junit的 assertEquals或者cactus提供的帮助类。
在这里需要提出的一点就是:代理不会去真正执行servlet,或filter,或jsp的代码,你需要在testXXX方法中调用或模仿这些代码。
书写servlet测试用例
import
java.io.IOException;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;
import org.apache.cactus.WebResponse;
public class TestSampleServlet extends ServletTestCase
{
public TestSampleServlet(String theName)
{
super(theName);
}
public static Test suite()
{
return new TestSuite(TestSampleServlet.class);
}
//这个方法在服务端运行,用来设置fixture
public void setup(){
}
//这个方法在服务端运行,用来释放fixture
public void teardown(){
}
//这个方法在客户端运行,可以用来设置请求参数
public void beginSaveToSessionOK(WebRequest webRequest)
{
webRequest.addParameter("testparam", "it works!");
webRequest.setURL("localhost", "test",
"SampleServlet" ,"gongys", "name=gongys");
}
//这个方法在服务端运行,用来具体进行代码测试
public void testSaveToSessionOK() throws
IOException
{
SampleServlet servlet = new SampleServlet();
servlet.saveToSession(request);
System.out.println(this.request.getPathInfo());
System.out.println(this.request.getParameter("name"));
this.response.getWriter().println("gongys");
assertEquals("it works!", session.getAttribute("testAttribute"));
}
//这个方法在客户端执行,用来验证返回结果
public void endSaveToSessionOK(WebResponse theResponse){
System.out.println(theResponse.getText());
}
} |
配置ant运行cactus测试
类路径的设置
我们要按照下面的图设置客户端(ant junit任务中)设置classpath,并把右半部分所示的类放到服务器或者webapp的类路径上
客户端cactus.properties
我们知道,cactus需要redirector 代理才能工作,我们除了把这些代理考到相应的webapp的类路径(对于filter和servlet代理)或webapp路径(对于jsp代理)外,我们还需要告诉客户端测试实例到哪里去找这些代理,下面是cactus.properties的内容:
cactus.contextURL = http://localhost:8080/test
其中test为被测试webapp的上下文路径。
cactus.properties也必须放在ant junit任务的classpath中。
服务器(假设为tomcat 4.12)server.xml的设置
我们必须在server.xml中添加cactus redirector代理,使得这些代理能接受客户端测试实例传过来的请求。详细添加办法请参见cactus
文档。
有了正确的junit 类路径的设置,其他的就合正常的junit测试一样。
1.4 clover测试覆盖率计算
clover覆盖率计算工具通过在被测源代码中插入相关指令,在被测源代码被执行时这些指令被执行,用以统计被测源代码被执行的次数,clover利用一个数据库来保存这些数据。Clover还提供了访问这个数据库的工具,并产生html报告文档。
配置ant运行clover分析
clover实现了一些ant任务,下面是ant中定义这些任务的代码
<taskdef resource="clovertasks"
> <classpath> <pathelement location="${clover.jar}"/>
</classpath> </taskdef> |
下面的代码初始化clover数据库:
<target name="with.clover"
depends="init"> <!-- 删除${build.dir}使得重新编译源代码
--> <delete dir="${build.dir}" />
<mkdir dir="${build.dir}" /> <clover-setup
initString="${user.home}/${ANTLOG_FILE_NOEXT}.db"
/> </target> |
下面的代码产生clover分析,格式为html,结果放在tempcloverreport目录中:
<target name="clover.html"
> <delete dir="tempcloverreport"></delete>
<mkdir dir="tempcloverreport" /> <property
name="clover.html" value="ok"<>/property>
<clover-report> <current outfile="tempcloverreport">
<format type="html"/> </current>
</clover-report> </target> <!--
下面用一个目标来初始化clover,编译源代码,unittest单元测试和clover分析--> <target
name="clover_report" depends="with.clover, compile,unittest,
clover.html"> </target> |
这个任务的工作原理为,with.clover在初始化clover数据库后,监视compile;在javac编译java源代码时把记录代码执行的相关指令插入到java源代码中;在单元测试时,这些插入的代码就开始记录被测试代码的执行次数,把结果输出到clover数据库中;clover.html目标根据数据库中的数据生成html文件。
需要注意的几点:
- 如果是执行cactus类的client/server测试,在服务端的类径中必须包含clover.jar类;
- clover 是一个商业工具,但可以得到30天的评估license;
- clover 在编译过程中改变了代码的执行路径,在产品发布时必须单独执行compile目标。
- Clover 分析结果
下面是Clover 分析结果的图示,读者可以自己看出从这个分析中能得到什么。第一个图是显示一个项目的整体覆盖率情况,第二个图显示了每一个类每行代码的覆盖情况。
1.5 statcvs项目度量工具
statcvs是一个利用cvs reporsitory log生成项目度量的工具,这些度量包括每个作者的代码量,每个目录或文件的代码行数。使用statcvs先要学会使用cvs。
Ant 中使用cvs
Ant 中使用cvs是通过cvs任务来完成的:
<property
name="cvsroot" value=":pserver:anonymous@10.1.36.135:/data/src"
/>
<!--取出源代码,放在tmp目录下,相当于执行cvs co -d ${base.path}/tmp/${location}
-->
<cvs cvsRoot="${cvsroot}"
package="${location}"
dest="${base.path}/tmp"
/>
<!-- 执行cvs log ,结果放在tmp.log中-->
<cvs dest="${base.path}/tmp/${location}"
command="log" output="${base.path}/tmp/${location}/cvs.log"/>
|
Ant 中使用statcvs
Statcvs实现了一个ant任务,下面是ant中定义这个任务的代码:
<taskdef name="statcvs"
classname="net.sf.statcvs.ant.StatCvsTask">
<classpath> <pathelement path="${statcvs.jar}"/>
</classpath> <</taskdef> |
下面是使用statcvs任务产生项目度量数据的代码,结果是一些html文件,放在${statcvs.htmldir}目录下:
<statcvs
projectName="${location}"
projectDirectory="${base.path}/tmp/${location}"
cvsLogFile="${base.path}/tmp/${location}/cvs.log"
outputDirectory="${statcvs.htmldir}"
/> |
1.6 velocity模版系统
velocity模版系统比起jsp模版来说有比较大的好处:
实现视图和控制代码的完全隔离
在jsp中,我们可以嵌入执行代码,jsp本质是具有格式化代码和控制代码混合能力,虽然大家发明了好多方法、设计模式和最佳实践,可是不能从根本上消除jsp编写员混合格式化代码和控制代码的恶习;而在velocity模版系统中,这种混合不可能存在,你不可能在velocity的.vm文件中通过代码Person
p = new Person()生成一个Java对象,这些业务对象只能在控制中生成并放到context中。
安全
jsp文件被编译之后形成了一个类似于servlet的东西,几乎可以在jsp中干任何事,你可以在jsp中写 System.exit(0)来关掉java虚拟机,或利用别的什么漏洞。
这里只说这些好处,关于其他的大家可以到网上去查或自己总结。下面我要介绍一下velocity模版系统工作机制和关于velocity的设置问题。
velocity模版系统工作机制
我们以在servlet环境下的模版系统为例(当然控制还可以由其他代码来实现)。控制可以实例化一些业务对象比如Person 放到context
中(执行context的相关方法),控制在接着装载相关的视图的模版比如PersonInfo.vm,产生Template实例,并让这个实例解释自己生成输出比如html格式流,Template实例在解释模版的时候会根据模版文件中的指令访问context中的业务对象。
所以要使这个模式工作,重要的一点是控制必须和视图就context中的业务对象的名字达成一致,这就是控制和视图的协议。
velocity的设置
velocity运行的第一个任务就是初始化,执行Velocity.init方法。无参数的init方法会采用缺省的属性配置,在velocity.jar
中的org.apache.velocity.runtime.defaults.velocity.properties位置;使用有参数的init方法,参数传递的是一个属性文件或java.util.Properties
对象,参数中定义的属性会覆盖缺省的属性设置,没定义的属性会采用缺省的属性设置。
比较有用的属性设置是读取模版文件时采用的字符集、产生输出流时使用的编码、模版所在的位置和模版装载器:
input.encoding = gbk
output.encoding = gbk
file.resource.loader.path = templates |
2、文档书写辅助工具
word 文档书写排版工具
powerpoint,图片组织绘画工具
visio 绘制数据流图,ER图等的工具
rational rose,绘制UML图形的工具
windows 附件中的画图来截取图片
操作系统的全屏打印功能
参考资料
进一步学习面向对象的系统分析和设计:《面向对象的系统分析和设计》Ronald J. Norman
《实用面向对象软件工程教程》殷人昆 田金兰 马晓勤 译
良好的用例编写风格可以从这里获得:《编写有效用例》 Alistair Cockburm
进一步理解cvs和nightlybuild技术的相关背景资料:《cvs和nightlybuild技术》 杨锦方
cvs源代码版本系统在:http://www.cvshome.org
statcvs 项目工作量分析工具在:http://statcvs.sf.net/
clover测试覆盖率分析工具在: http://www.cortexebusiness.com.au/
ant构建工具在:http://ant.apache.org
junit单元测试工具在:http://www.junit.org
apache web程序测试工具在:http://jakarta.apache.org/cactus/
关于作者
龚永生 (gongys@legend.com)
北京市海淀区上地信息产业基地开拓路7号联想大厦 |