UML软件工程组织

揭开极端编程的神秘面纱:  工作的首选(XP)工具
Roy W. Miller (rmiller@rolemodelsoft.com)
Software Developer, RoleModel Software, Inc.
从Eclipse开始
JUnit
使用Ant来构建
工具的优势
希望尝试XP的团队经常不知道从何开始。通常他们有太多关于XP的问题。但技术之后将是什么呢?这个月,Roy Miller结合理论和实践来讨论您应使用什么工具以及如何使用它们。请在附带的讨论论坛中与作者和其他读者一起分享您有关本文的心得体会(您也可以点击文章顶部或底部的讨论来访问论坛)。

当谈到您实施XP需要的工具时,坏消息是您通常需要学习一些新知识。好消息是您首先只需要少数几件工具。您无需大量文件创建工具和设计工具来形成一个大软件。最后您只需要一些非技术性设备,少数软件和一些专职团队成员。记住:XP就是做最简单的事,这可以应用于设计、代码、流程工具。

我已经编写软件和管理项目十年了。在那段时期,我使用大量优秀的工具来管理和编程。在这样做之后,我可以心安理得的说人们太依赖于工具了。工具有自己的一席之地,但不能替代技能和经验。

我建议您和您的团队以相对简单的工具开始(可能是继续使用),它们可以提供大量的功能,为您和您的团队免去了繁重的学习之苦。假设您一直在使用Java语言(实际上,这就是Java技术专区),在您的项目中可以考虑使用以下工具:

  • Eclipse,作为您主要的团队友好的开发环境
  • 您最喜欢的源代码控制工具(我使用CVS的时间与使用Eclipse的时间一样长)
  • JUnit用于编程人员测试并可以作为客户测试的一个引擎
  • HttpUnit用于测试Web应用程序
  • Ant用于您每天开发自己的应用程序
  • Shams用于在需要它们存在的测试中,剔除您没有构建的组件

我们将把本文的重点放在有效使用Eclipse、JUnit和Ant上。(HttpUnit是下一篇文章的主题,我们在前一篇文章“测试驱动的编程”中介绍了Shams。)

从Eclipse开始
在我的编程人员职业生涯中,我使用了多种IDE。当我第一次开始使用VisualAge for Java (VAJ)时,需要学习的知识都快把我淹没了,但在两三个星期之后,我奇怪为什么其它人不使用VAJ也可以编写Java软件。这是我使用过的最令人惊讶的IDE。而且,Eclipse性能更优越 -- 并且它是免费的(真的)。

Eclipse基于一个简单的假设:团队应有一个真正集成的开发环境。整个应用程序都使用Java语言来编写,以便使用Java语言来进行开发。如果您希望看到功能丰富、well-factored的代码,那么看看Eclipse的源代码吧!该应用程序真正是构建应用程序的一个框架,基于插入式架构。几乎Eclipse中的所有组件都是插入式的 -- 甚至Java语言开发支持,它有一个任何组件都可插入的核心平台。在Eclipse站点上和developerWorks(见参考资料)提供了大量关于如何开始的文章,因此我将简要介绍如何使用这一工具。

安装很简单。登录Eclipse下载页面,选择一个镜像站点然后下载最新的stable build或最新版本(2.1)。不要认为您必须等待新版本来升级Eclipse --Eclipse团队的目标很明确,测试第一,他们提供整套测试,因此您可以核查以确保stable build是稳定的。

在下载了该版本之后,完成以下步骤来安装和运行这一版本(假设您使用Windows操作系统):

  1. 安装JDK 1.4(如果您没有JDK1.4,关于其下载的相关信息请参阅参考资料)。
  2. 将Eclipse压缩文件解压缩到C:\eclipse。
  3. 在您的硬盘上建立单独的目录,叫做C:\EclipseWorkspaces
  4. 在开始菜单上建立C:\eclipse\eclipse.exe -data C:\EclipseWorkspaces\<your workspace name>的快捷方式,从C:\eclipse中开始。
  5. 双击新快捷方式。Eclipse将在C:\EclipseWorkspaces中建立workspace的子目录。

第一次运行Eclipse时,您将看到与图1类似的界面:

图1:Eclipse欢迎界面
Eclipse Welcome screen

欢迎界面是Eclipse Workspace资源视图的一部分。这一视图使您能够查看所有文件。该表在导航视图的左上角显示。Eclipse完全可定制,您可以将视图移到您希望的任意位置。目前我们将它们单独放置并转向Java Browsing视图。通过选择Window >Open Perspective,您可以发现那个视图可用。选择Java Browsing视图,如图2所示:

图2:Java Browsing视图
Java Browsing perspective

Java Browsing视图在顶部有四个视图,在底部有一个编辑器。四个视图从左到右显示项目、包、类型和成员。包、类型和成员是您应该很熟悉的Java语言结构。项目是Java包的Eclipse Meta容器。您可以在这一视图上进行大多数开发工作,而资源视图更便于处理文件而不是源代码和类文件。

要开始开发,完成以下步骤以创建称为Sample的Java项目:

  1. 点击 Window >Preferences >Java >New Project
  2. 点击Folder单选按钮,然后点击OK
  3. 点击 File >New >Project.
  4. 选择 Java Project,然后点击Next
  5. 输入Sample作为项目名称,然后点击 Finish

现在您应该在项目视图中看到Sample项目。此时,点击工具栏上的Create a Java Package按钮,创建称为com.sample的包。选择这一包。现在您已经准备创建您的第一个Java类,了解Eclipse如何帮助继续进行XP开发。

JUnit
XP是过去十年内我的软件开发职业生涯中单个最大的影响。作为一位编程人员,受最大影响的实践(Practice)(XP或其它方面)是在我编写代码之前编写测试程序。正如我上月所述,编写足够的代码以使测试通过可以简化程序代码,从而有更多的精力关注手边的工作。上个月我讨论了一些关于使用JUnit的技巧,在此处我将进行详细阐述。

JUnit与Eclipse一同提供,因此您无需进行下载。如果您不使用Eclipse,您可以下载JUnit (见参考资料)。要使用JUnit,您必须首先将JUnit JAR保存在项目的Build路径上并创建一个测试类。完成以下步骤以将JUnit保存在项目的Build路径上:

  1. 右击Sample项目,选择菜单底部的Properties
  2. 选择Java Build Path
  3. 选择Libraries标记
  4. 点击Add Variable按钮
  5. 点击New按钮,输入JUNIT_LIB作为变量名称
  6. 编辑该变量并指向C:\eclipse\plugins\org.junit_3.8.1中的一个文件(JUnit是Eclipse插件)
  7. 再次选择您Sample项目中的src文件夹

现在您把JUnit保存到了您的Build路径上。您可以直接向该路径添加外部JAR,但使用变量可以更简单的设置其它机器上的Workspaces(该变量是可以指向面向机器的位置的Meta名)。下一步是创建测试类:

  1. 点击工具栏上Create a Java Class按钮右侧的下拉箭头,选择Test Case
  2. 输入TC_Account作为测试名
  3. 选择setUp()tearDown()复选框
  4. 点击Finish

现在您应该看到与图3类似的一个界面:

图3:打开测试案例
Open test case

您现在在类型视图中显示了TC_Account类,您可以在成员视图中看到该类的方法。您还在TC_Account类上打开了一个编辑器,编辑器中显示一些生成的代码和注释。我喜欢设置我自己的偏好来阻止显示所有生成的注释,您可以通过选择Window >Preferences >Java >Code Generation来实现。

Account类将做什么?我们从能够向账户添加存款开始。这可能需要一种与下面类似的方法:


public void deposit(int amount)

TestCase添加一种方法将测试Accountdeposit方法。测试类现在看起来应像表1一样:

表1:JUnit测试

package com.sample;

import junit.framework.TestCase;

public class TC_Account extends TestCase {

	public TC_Account(String arg0) {
		super(arg0);
	}

	protected void setUp() throws Exception {
		super.setUp();
	}
	
	public void testDeposit() {
		Account account = new Account();
		assertEquals("Account should start with no 
		  funds.", 0, account.balance());
	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}

}

使用英语,您正在核查account.balance()返回0。注意测试甚至不进行编译,因为Account类并不存在。图4显示了当测试不编译时Workspace的显示界面:

图4:不进行编译的测试案例
Test case that doesn't compile

界面底部的任务视图显示编译错误。点击任何错误将把您带到代码中真正出错的位置,它非常方便。实际上,Eclipse提供了一些象这样的方便之处。例如,注意有多种方法来表示您有编译错误。任务视图显示它,编辑器在左侧标记红色的X圆圈,workspace顶部的所有视图显示一个红色的X。如果您把鼠标盘旋在编辑器左侧的红色X上(在行附近显示错误),hover text向您提供错误信息。

您可以随意盘旋在任何物体上,点击物体,进行另外一次实验以查找“隐藏”属性。它们遍布各处。但回到手头上的工作 -- 测试没有编译。因此,编写足够的代码来使测试进行编译、运行但结果失败。值得注意的是,我们正在努力采取一些基本措施并使事情尽可能简单并尽可能长的运行。通过选择com.sample项目来创建Account类,点击工具栏上的Create Java Class按钮,输入Account作为类名,然后点击Finish。您现在应在Account类上打开了一个编辑器,它应该无任何方法。添加balance()方法。该类现在看起来应与表2类似:

表2:Account类

package com.sample;

public class Account {
	public int balance() {
		return 0;
	}

}

要进行测试,选择TC_Account类,点击工具栏上“测试人员(running man)” 图标附近的下拉箭头,选择Run As >JUnit Test。测试将进行并将显示作为界面底部的一个视图。我喜欢使JUnit成为Fast视图,将其拖到workspace左侧,直到Eclipse让我把JUnit视图拖到Fast视图栏为止。此时,选择Window >Preferences >Java >JUnit,选中Show the JUnit results view only when a failure or error occurs复选框。这将隐藏JUnit Fast视图,除非发生错误或失败。如果一切都正常运行,它将在图标的左下角显示一个绿色标记。.

编写足够的代码以使测试进行编译此时将为您提供即将通过的测试程序,但不是长期。现在向测试案例添加另一种断言,以在某些人调用deposit()方法后测试新的余额。testDeposit()方法现在看起来应与表3类似:

表3:JUnit的deposit()方法测试

public void testDeposit() {
	Account account = new Account();
	assertEquals("Account should start with 
	  no funds.", 0, account.balance());

	account.deposit(5);
	assertEquals("Account should reflect 
	  deposit.", 5, account.balance());
}

编写足够的代码以使测试进行编译。在这种情况下,这意味着向Account增加do-nothing deposit()方法。该类现在看起来应与表4类似:

表4:使用空deposit()方法的Account

package com.sample;

public class Account {
	protected int balance;
	
	public int balance() {
		return balance;
	}
	
	public void deposit(int amount) {
	}

}

再次点击测试人员图标来重新进行测试。JUnit Fast View显示,您将看到显示“Account should reflect deposit”信息的故障。现在您有一个即将失败的测试程序,它将告诉您真正需要编写那些代码。编写足够的代码来确保测试通过。使用deposit()方法将存款额添加到balance应毫于问题。当您重新进行测试时它们应该通过。

这种“编写一个测试,编写足够的代码来使测试通过,重新进行测试”方法是您每天都在经历的XP开发流程。由于JUnit集成到Eclipse中,您需要的关于编程的一切都可以满足。运行测试很简单,就象呼吸一样。创建它们也是非常简单,因为通过生成代码,Eclipse为您保存了大量常见的输入。您只需要进行思考和考虑重要的事情。您的Account类现在看起来应与表5类似:

表5:实施了deposit()方法的Account

package com.sample;

public class Account {
	protected int balance;
	
	public int balance() {
		return balance;
	}
	
	public void deposit(int amount) {
		balance += amount;
	}

}

能够存款是好事,但人们可能想要把它取出来。为Accountwithdraw()方法编写一个测试。您的测试现在看起来应与表6类似:

表6:更新的Account测试

package com.sample;

import junit.framework.TestCase;

public class TC_Account extends TestCase {

	public TC_Account(String arg0) {
		super(arg0);
	}

	protected void setUp() throws Exception {
		super.setUp();
	}
	
	public void testDeposit() {
		Account account = new Account();
		assertEquals("Account should start with 
		  no funds.", 0, account.balance());
	
		account.deposit(5);
		assertEquals("Account should reflect 
		  deposit.", 5, account.balance());
	}

	public void testWithdraw() {
		Account account = new Account();
		account.balance = 5;
		account.withdraw(3);
		assertEquals("Account should reflect 
		  withdrawal.", 2, account.balance());		
	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}

}

通过向Account增加do-nothing withdraw() 方法来使测试进行编译,然后重新运行测试。测试失败。现在实施withdraw方法。Account类看起来应与表7类似:

表7:实施了withdraw()方法的Account

package com.sample;

public class Account {
	protected int balance;
	
	public int balance() {
		return balance;
	}
	
	public void deposit(int amount) {
		balance += amount;
	}
	
	public void withdraw(int amount) {
		balance -= amount;
	}

}

所有测试都应通过。现在是时候进行集成了。您将回忆起在XP中“连续集成”是一项重要的实践。任何时候所有测试通过,您可以将代码集成到系统中。您应尽可能早并经常进行集成。Eclipse使这一切变得异乎寻常的简单。

集成,集成,集成
为了准备您的Eclipse workspace进行集成,完成以下步骤;您将需要设置CVS:

  1. 选择Window >Open Perspective >Other >CVS Repository Exploring,打开CVS视图。
  2. 右击并选择New Repository Location。输入与您的特殊设置有关的所有参数,然后点击Finish。在CVS Repositories视图的表中应显示一个存储位置(Repository Location)。打开它您将看到一个HEAD流、一个Branches条目和一个Versions条目。HEAD条目是用户应集成的主要代码流(如果它们需要自己分叉并稍后与HEAD流合并,这主意不错)。
  3. 关闭CVS视图窗口。

要将代码集成到系统中,完成以下步骤:

  1. 右击Sample项目。
  2. 选择Team >Share Project。您正在“共享”一个以前未集成的项目。您通常只需这样做一次。
  3. 根据提示,选择您希望的存储位置,然后点击Finish.

现在XP流程应该是这样:“编写一个测试,编写足够的代码来使测试通过,重新运行测试,集成。”

现在我们来看一看TC_Account。注意是否有任何代码复制的现象?这两种测试方法都例示一个Account。JUnit框架在每个测试方法之前运行setUp()方法,因此这就是进行所有测试需要的任何设置的逻辑场所。例示Account对象合格。

这是简单的refactoring,但Eclipse使其甚至更简单:

  1. 进入TC_Account的编辑器
  2. 右击testDeposit()中的account局部变量,然后选择Refactor >Convert Local Variable to Field.
  3. 输入“account”作为字段名,选择protected访问限制符(access qualifier),然后点击OK。现在您的测试类有一个叫做account的受保护的字段,测试方法对其进行了初始化。
  4. 将初始化account的代码从测试方法移到setUp(),然后删除其它测试方法中的声明和初始化。您的测试类现在看起来应与表8类似:
表8:Refactored TC_Account

package com.sample;

import junit.framework.TestCase;

public class TC_Account extends TestCase {

	protected Account account;

	public TC_Account(String arg0) {
		super(arg0);
	}

	protected void setUp() throws Exception {
		super.setUp();
		account = new Account();
	}
	
	public void testDeposit() {
		assertEquals("Account should start with 
		  no funds.", 0, account.balance());
		
		account.deposit(5);
		assertEquals("Account should reflect 
		  deposit.", 5, account.balance());
	}
	
	public void testWithdraw() {
		account.balance = 5;
		account.withdraw(3);
		assertEquals("Account should reflect 
		  withdrawal.", 2, account.balance());		
	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}

}

您还得做一些工作,但Eclipse至少为您减少了一些单调乏味的输入工作。检查上下文菜单中其它refactorings变量。其中一些变量功能非常强大,可以节约您大量的时间:

  1. 进入Account编辑器,然后点击balance()方法名。
  2. 选择Refactor >Rename,然后输入“getBalance”作为新名称。
  3. 确信选中“Update references to the renamed element”,然后点击OK

这一简单的练习重命名了Account的accessor方法并更新了所有引用。重新运行测试以确保一切仍在正常运行。对于一个类来说,这可以很轻松地节约一分钟的时间,或者减少大量输入工作。想像一下,您有一个巨大的系统并有多个类调用getBalance,结果又将如何。您可以自由地在街上跳舞了!并且最重要的一件事是refactorings是可以撤销的。如果您输入了“getbalancew,”只需撤销就可以了。更有甚者,由于它如此简单,您只需再次refactor并更改这一名称。

是时候进行再次集成了。记住流程了吗?我们更改了一些代码并重新进行了测试:

  1. 右击Sample项目,然后选择Team >Synchronize with Repository来打开界面底部的Synchronize视图。双击蓝色标题栏来打开它。
  2. 点击工具栏右侧的Incoming/Outgoing Mode按钮。这一视图向您显示所有incoming和outgoing变化。换句话说,Eclipse联合CVS来向您显示workspace中内容与CVS内容之间的Delta。
  3. 由于您以前没有提交任何事情,右击Structure Compare视图中的Sample项目,然后点击Commit

想了解如果workspace中内容与CVS内容之间有Delta将会发生什么,返回Account类,右击deposit()方法的amount参数,然后选择Refactor >Rename,将名字更改为“anAmount。”瞧,结果发生了变化。重新运行测试。一切都应通过。现在右击该项目并再次同步。您应看到一个与图5类似的界面。如果您双击Account类,您将看到您刚创建的Delta。因为您想要保留它,右击该项目然后选择Commit

图5:Synchronize视图
Synchronize view

使用Ant来构建
在项目启动后,每个XP团队都应在每天结束时构建它们整个系统。从而团队能够向需要了解的任何人(例如,客户)提供正在运行的系统。它还为团队提供检查点(checkpoint)。如果系统某一部分发生严重故障,它们可以总是从昨天返回正在运行的系统。没有任何一项功能能象安全网一样使团队更具信心。

Jakarta的Ant项目是很好的构建工具。您使用XML来编写构建脚本程序。我发现XML“目标”有点神秘,当您对它越来越熟悉时这种神秘感也就慢慢裉去了。但是,我必须承认,如果没有使用Ant一段时间,我必须查找大量的信息来完成基本的工作。任何情况下,Eclipse都与一个Ant插件一同提供。集成是近乎无缝的。

现在我们为我们的项目-将文件复制到硬盘驱动器(在实现环境中,这可以是网络驱动器或一些其它驱动器)某处的部署目录-创建一个实例:

  1. 选择workspace中的Sample项目
  2. 选择File >New >Other
  3. 选择Simple,然后选择File
  4. 命名新文件为build.xml。Eclipse创建该文件并在它上面打开一个编辑器(注意标题栏中的图标是Ant图标)。您的Build脚本程序看起来应与表9类似:
表9: Build脚本程序

<project name="Sample" default="build" basedir=".">
	<property name="project" value="${basedir}"/>
	<property name="tempDirectory" value="${project}/temp"/>	
	<property name="runtimeClasses" 
	  location="${project}/lib/runtime.jar"/>
	<property name="deployDirectory" location="c:/deploy"/>

	<patternset id="non.test.classes" >
			<include name="**/*.class"/>			
			<exclude name="**/TC_*.class"/>
	</patternset>

	<target name="build">
		<antcall target="clean"/>
		<antcall target="init"/>
		<antcall target="build.Sample"/>
		<antcall target="clean"/>
	</target>
	
	<target name="init">
		<mkdir dir="${tempDirectory}"/>
		<mkdir dir="${deployDirectory}"/>
	</target>

	<target name="build.Sample">
		<javac srcdir="${project}/src"
			destdir="${tempDirectory}"
		>
			<exclude name="**/TC_*.java"/>
		</javac>

		<jar destfile="${deployDirectory}/sample.jar">
			<fileset dir="${tempDirectory}"></fileset>
		</jar>

	</target>

	<target name="clean">
		<delete dir="${tempDirectory}"/>
	</target>	
</project>

Ant基于目标,它描述Ant运行的工作单元。在这种情况下,您有三个目标。第一个是主目标,称为buildproject标记的default属性确定build作为缺省目标。这一目标调用其它三个:cleaninitbuild.Sample。如果它使用的是Java代码,您可以描述缺省目标为delegator方法 -- 它按正确的顺序调用其它目标(在某种意义上,它是Template Method模板的实施)。这种主要目标首先调用clean来确保系统开始刷新,然后调用init来设置必需的目录,接下来,它调用build.Sample然后再次调用cleanbuild.Sample目标是这项操作的所在地。使用build.Sample,您可以:

  • 在本地硬盘驱动器上创建部署目录
  • 编译您所有的Sample项目源代码,不包括测试案例(无需部署它们)到临时目录
  • JAR将编译后的类保存到临时目录并把它们保存到部署目录

要运行Ant脚本程序,进入Resource视图,右击build.xml,选择Run Ant,它将弹出一个选定了缺省目标的对话框。点击Run。如果出错,Eclipse将在界面底部显示Ant Console。这是我对Ant最不满意的地方:如果发生错误,调试将是一种负担。幸运的是,Echo Ant目标为您提供等同于System.out.println()的功能。如果在您的脚本程序中发生错误,加入 <echo>some helpful message</echo>将帮助您了解发生了什么。在当前情况下,错误相当棘手。Ant抱怨它不能找到tools.jar。您需要告诉Ant什么地方能找到编译需要的Java类。要实现这一目标,按下步骤操作:

  1. 选择Sample项目
  2. 选择File >Import,然后选择File System.
  3. 浏览您安装的JDK中的lib目录
  4. 选择tools.jar 文件,然后点击Finish

完成这一系列步骤可以在Sample项目中创建一个lib目录并把tools.jar放入其中。现在告诉Ant如何找到它:

  1. 选择Preferences >Ant >Runtime。界面底部显示“Additional classpath entries:”。
  2. 点击Add JARs... 按钮,然后选择Sample/lib/tools.jar。现在Ant知道如何找到您的编译器。
  3. 右击build.xml然后重新运行Ant。程序应无任何错误,您应该以将sample.jar 保存在c:\deploy中结束。

工具的优势
您将注意到在“must have”表中没有多少工具。您可能希望获得其它Eclipse插件,您还可能需要一些额外的Java语言库来支持您的特殊项目,但您不需要一大堆项目团队假设它们需要的工具。如果您正在使用XP,您唯一需要的管理工具是一些记录卡和一个电子表格。每样东西都是开发人员的工具。我认为您应该反对增加更多的插件,除非它们能使工作更加轻松。如果它们能做到,那就把它们加进来。好的工具能够使您享受工作。Eclipse、JUnit(以及其它测试助手,如shams和HttpUnit)和Ant都是我经常使用的工具。如果您需要一个应用程序服务器,使用Tomcat直到有人告诉您不允许这样做。

 

版权所有:UML软件工程组织