2005年08月29日
一、概念

Martin Fowler的文章:Continuous Integration  中文翻译:持续集成

二、工具

传统工具:JBuilder,VisualSourceSafe,Rational ClearCase

自动编译工具:Ant

回归测试工具:JUnit

代码检查工具:CheckStyle

持续集成工具:CruiseControl

三、步骤

  • CruiseControl监控远程版本控制系统的变化

  • 变化发生时CruiseControl调用编译工具进行编译(Ant等)

  • 编译成功后调用JUnit进行回归测试

  • 编译成功后调用CheckStyle进行代码检查

  • 打包,部署,更新版本控制系统

  • 完毕后将编译结果、测试结果、代码检查结果发送至开发人员、主管经理,并发布至网站,甚至报警器

    所有这一切都是按照编制好的脚本自动进行的

四、实施示例

目前我们使用的是ClearCase,主控软件为CruiseControl,其脚本文件为cccc.xml

  • 配置远程版本控制系统

<modificationset quietperiod="60">

  <clearcase branch="main" viewpath="D:\cc_view\chelseafc\Nucleus2.0\Port" recursive="true" />

</modificationset>

  • 配置编译工具

<schedule interval="60">

  <ant antscript="C:\Java\JBuilder2005\thirdparty\ant\bin\ant.bat" buildfile="D:\cc_view\chelseafc\Nucleus2.0\Port\clearcase-build.xml" target="cleanbuild" multiple="1" />

</schedule>

  • 配置测试用例(在ant的配置文件中)

<target name="test" depends="init" description="Run unit tests">

  <delete dir="${junit.results}" />

  <mkdir dir="${junit.results}" />

  <junit fork="yes" haltonfailure="yes">

  <classpath refid="project.class.path" />

  <formatter type="plain" usefile="false" />

  <formatter type="xml" />

  <batchtest todir="${junit.results}">

  <fileset dir="${build.dir}" includes="**/*TestCase.class" />

  <fileset dir="${build.dir}" includes="**/*TestSuite.class" />

  </batchtest>

  </junit>

</target>

  • 配置报告形式

<publishers>

  <currentbuildstatuspublisher file="currentbuild.txt" />

  <htmlemail mailhost="mail.chelseafc.com.cn" returnaddress="workflow_engine@chelseafc.com.cn" subjectprefix="ContinuousIntegration:" buildresultsurl="http://chelsea:8044/cruisecontrol/buildresults" spamwhilebroken="true" xsldir="F:\software\Agile.Net\cruisecontrol-2.2\reporting\jsp\xsl" css="F:\software\Agile.Net\cruisecontrol-2.2\reporting\jsp\css\cruisecontrol.css" logdir="D:\Tomcat 4.1\webapps\cruisecontrol\samplelogs">

  <always address="chelsea@chelseafc.com.cn" />

  <always address="ajax@chelseafc.com.cn" />

  <map alias="chelsea" address="chelsea@chelseafc.com.cn" />

  </htmlemail>

  </publishers>

  • 其中CruiseControl暂时没有提供代码检查工具的支持,建议使用Ant来调用CheckStyle,示例如下(没有真正运行过):

<target name="web.checkstyle">

  <mkdir dir="${target.temp}/checkstyle" />

  <mkdir dir="${target.web}/checkstyle" />

  <taskdef resource="checkstyletask.properties">

  <classpath>

  <fileset dir="${support.tools}/checkstyle31" includes="**/*.jar"/>

  </classpath>

  </taskdef>

  <copy file="${support.tools}/checkstyle31/custom.xml" overwrite="true" tofile="${target.temp}/checkstyle/custom.xml">

  <filterset>

  <filter token="source.java" value="${basedir}/${source.java}" />

  <filter token="target.checkstyle" value="${basedir}/${target.temp}/checkstyle" />

  </filterset>

  </copy>

  <checkstyle config="${target.temp}/checkstyle/custom.xml" failOnViolation="false">

  <fileset dir="${source.java}/main" includes="**/*.java" />

  <formatter type="plain" />

  <formatter type="xml" toFile="${target.temp}/checkstyle/checkstyle_errors.xml" />

  </checkstyle>

  <style basedir="${target.temp}/checkstyle" destdir="${target.web}/checkstyle" includes="checkstyle_errors.xml" style="${support.tools}/checkstyle31/checkstyle-noframes.xsl" />

  </target>

  • 打包、部署(在ant的配置文件中)

<target name="jar">

<jar jarfile="${dist.dir}\nucleus_engine.jar">

  <fileset dir="${build.dir}" />

</jar>

<jar jarfile="../../M1/Web/lib/service_gateway.jar">

  <fileset dir="${build.dir}" />

</jar>

</target>

<target name="war">

  <ant antfile="build-war.xml" />

</target>

  • check out 版本控制工具中的配置项(在ant的配置文件中)

<target name="checkoutzipfile">

  <cccheckout viewpath="../Release/nucleus2.0.zip" reserved="true" branch="main" />

</target>

  • 压缩zip文件(在ant的配置文件中)

<target name="zip">

  <zip destfile="../Release/nucleus2.0.zip" basedir="../Release/nucleus2.0" />

</target>

  • check in 版本控制工具中的配置项(在ant的配置文件中)

<target name="checkinzipfile">

  <cccheckin viewpath="../Release/nucleus2.0.zip" identical="true" />

</target>

五、几点提示

  • CruiseControl会自动根据本地ClearCase的View监控远程VOB

  • 其实除了监控远程版本控制系统外其它的任务都可以由Ant来完成,CC只负责监控变化并调用Ant即可

  • CruiseControl2.2的<htmlemail>好像无法在jdk5.0下运行,如果需要集成jdk5.0的项目,则需要为Ant的<javac>指定source和target,而CruiseControl2.2依然用jdk1.4启动

  • 可以为cruisecontrol.bat加入启动参数“-port 8055”,这样可以用JMX(http://localhost:8055)来控制cc

  • 最好避免中文路径,否则就需要手工为几个Xml格式的文件,如cc的report Servlet的Web.xml等加入编码方式“<?xml version="1.0" encoding="UTF-8" ?>,或者将中文路径映射为虚拟硬盘:“subst Y: "D:\cc_view\chelsea\Platform\开发\Nucleus2.0\Source"”

  • 中文log无法正常显示时,需要设置CruiseControl配置文件中<log>元素的“encoding”属性,如:

    <log dir="D:\Tomcat 4.1\webapps\cruisecontrol\samplelogs" encoding="utf-8">

      <merge dir="D:\cc_view\chelseafc\Nucleus2.0\Port\test-results" />

    </log>

  • 编译失败后,在下次checkin之前,一般不需要重新编译,这时可设置<project >的“buildafterfailed”属性为false来避免重新编译

  • <htmlemail>的几个属性好像没有缺省设置,虽然文档里说从2.1.7开始有缺省设置,包括xsldir,css,logdir

  • 可使用JBuilder辅助生成ant文件

  • 各种工具的安装、使用,在各自的文档里都非常详细,网上亦有无数资源

六、参考资料

2005年08月23日

  这是对(三)中问题的一些思考。

  对于一个Bean来说,如果它含有对自己属于检验的函数,我就把它叫做可以自检的Bean,反之就称做非自检函数,非自检的函数就需要其它人(主要是Action)的监督。

  以一个Person类为例。

  public class Person{

            …

             private int age;

             public void setAge(int age){this.age=age;}

             public int getAge(){return this.age;}

             …

        }

  这个Bean中并没有任何检测,这样的Bean在应用中会出现很多问题,比如说一个人的年龄为负数,这显然是不合理的。但这个检测应该由谁来做呢?由Bean自己来做,还是由Action来做?

  这就是一个自检和监督的问题,我们应该更侧重哪一方面?

  我个人而言,我更侧重于监督,自检为了适应更广泛的应用,只能对Bean较少的,最基本的检测,而其它大部分的事情都应该由其它人来做。

对于每一个Action来说,都应在Monitor中对Action的监督条件进行说明,由Monitor来进行监督,Action将所有监督的职责交给Monitor

监督的好处:

1.    Bean来说,他只存储信息,对Action来说,他只处理业务逻辑,而其中的所有检测工作都交给了Monitor

2.    检测条件有时候会发生改变,对于不同的Action改变会有所不同,但是他们可能应用同一个Bean。在条件发生改变时,针对Action只改变Monitor中的检测即可。

3.    BeanAction都从数据检测中脱离出来,加强数据的整洁度。

个人想法,希望会对大家有用。

2005年08月22日

  今天在做东西的时候发现关于validate的一点点问题。

  struts在对bean的验证时,把这个任务交给了bean,但是我感觉对bean这项工作应该交给action。

  我觉得应该由Action决定Bean有什么样的权力或者说属性值,虽然说Bean应该了解自己,也就是说Bean应该来验证它自己是否合格,但是对于不同的Action来说,不同的Bean有不同的属性值,就像一个人是否生病,虽然可以由自己进行判断,但是还是应该由医生来诊判。并且在诊判的时候来要去不同的部门。这是将Bean应用于不同的Action的一种想法,但是如果将Bean应用于完全相同的Action中的话,那么应该是在Bean中验证的,这样减轻的Action的负担。

  这只是我一时的想法,也许设计者在设计的时候想到的更多,在做了权衡之后才选择了这种方案。希望理解的人能够解除我的困惑。

2005年08月19日

    在笔记(一)中对struts的一些理解有一些错误,在查阅一些资料后在此将它做些修改.当然,这次修改也不能保证其正确性,只是我这时的理解.

    在(一)中对ActionMapping的理解是错误的,ActionMapping并没有保存了Action的实例,这些实例是由RequestProcessor中的actions域来保存的,其源码如下:

protected HashMap actions = new HashMap();

当有一个请求时调用Action processActionCreate(HttpServletRequest request,

                                         HttpServletResponse response,

                                         ActionMapping mapping)

        throws IOException

函数来得到一个所请求的Action实例,这个函数维护actions域,由源码可以推测,这个actions是一个共享域,应该是由整个应用程序共享的:

synchronized (actions) {…}

在这个代码块中向action中添加或取出一个action的实例.

相对于Action来说,ActionMapping应该是Action的一个装饰器,它继承了ActionConfig,只添加了三个函数,对于不同的请求,都只有一个Action的实例,但是用的ActionMapping来设置此次请求的相关环境,这些ActionMapping是由ModuleConfigImpl来维护的,可以能过ActionMapping的path属性来查找一个ActionMapping的实例.

在这次的学习中了解了Action的一些知识,对struts有了更进一步的理解.在学习过程中只是提出了一些自己的理解,难免有不对之处,望指正.

  通过这几天对Struts的学习,对struts算是小有成效,把自己对struts的一些理解和看法写出来,希望高手指正.

 在web.xml的配置中可以看出.所以的请求都被发送到org.apache.struts.action.ActionServlet类.这个类是一个servlet(我想应该是的),这个servlet我想应该算是controller,页面发送的请求被tomcat封装为HttpServletRequest类的一个实例,(而这个实例正是让我思考了很久的地方).请求被发送到ActionServlet后,该servlet解析路径,得到所请求的Action的名称,用该名称通过struts-config.xml即可找到类名,通过java的反射机制就可以生成该Action的一个实例(这里我想应该是在加载的时候所做的工作,即在加载的时候生成每个Action的一个实例,把这些类放在ActionMapping里,因为每一个Action都是唯一的.).servlet得到Action的一个实例后即调用execute(ActionMapping,ActionForm,HttpServletRequest,HttpServletResponse)函数,

这里的ActionForm是根据提交的数据和FormBeans中所定义的bean类生成的bean实例.HttpServletRequestHttpServletResponse就是对ActionServlet请求的requestresponse.execute执行后返回ActionForwardActionServlet根据它来确定下一页面.但是下一页面不论是jsp页面,还是一个servlet,还都是应用ActionServlet的请求(没设置redirect时,这三个地方都用的同一个request,这样就可以在页面间传送数据,比如说一个对象.).这相servlet中可以保存数据后由jsp页面显示.

       这只是我基于这几天我的学习,和自己的想法写出来的,我并没有看到源码,有一些方面可能会出现理解上的错误,还望高手赐教。

2005年08月15日

webwork还没学明白就要换struts了,真是无奈啊.

2005年07月27日

    今天看了看webwork,觉得还不错。可以好好学习一下。可是有很多地方还不是很懂。