一、前言
在二十三天中我们介绍了使用maven来下载工程的依赖库文件,用ant来进行war包的建立。今天我们在这个基础上将使用junit+dbunit来进行带有单元测试报告的框架的架构。
目标:
- 每次打包之前自动进行单元测试并生成单元测试报告
- 生成要布署的打包文件即war包
- 单元测试的代码不能够被打在正式的要布署的war包内,单元测试仅用于unit test用
- 使用模拟数据对dao层进行测试,使得dao方法的测试结果可被预料
二、Junit+Ant生成的单元测试报告
<target name="junitreport"> <junit printsummary="on" haltonfailure="false" failureproperty="tests.failed" showoutput="true"> <classpath> <pathelement path="${dist.dir}/${webAppQAName}/WEB-INF/classes" /> <fileset dir="${lib.dir}"> <include name="*.jar" /> </fileset> <fileset dir="${ext-lib.dir}"> <include name="*.jar" /> </fileset> </classpath> <formatter type="xml" /> <batchtest todir="${report.dir}"> <fileset dir="${dist.dir}/${webAppQAName}/WEB-INF/classes"> <include name="org/sky/ssh/ut/Test*.*" /> </fileset> </batchtest> </junit> <junitreport todir="${report.dir}"> <fileset dir="${report.dir}"> <include name="TEST-*.xml" /> </fileset> <report format="frames" todir="report" /> </junitreport> <fail if="tests.failed"> --------------------------------------------------------- One or more tests failed, check the report for detail... --------------------------------------------------------- </fail> </target>
在一般的产品级开发时或者是带有daily building/nightly building的项目组中我们经常需要检查最新check in的代码是否影响到了原有的工程的编译,因为每天都有程序员往源码服务器里check in代码,而有时我们经常会碰到刚刚被check in的代码在该程序员本地跑的好好的,但是check in源码服务器上后别人从源码服务器“拉”下来的最新代码跑不起来,甚至编译出错,这就是regression bug,因此我们每天的打包要干的事情应该是:
- 程序员check in代码时必须把相关的unit test也check in源码服务器
- 次日的零晨由持续集成构件如:cruisecontrol自动根据设好的schedule把所有的源码服务器的代码进行编译
- 运行单元测试
- 生成报告
- 打包布署到QA服务器上去
三、如何在Spring下书写一个单元测试方法
3.1使用spring的注入特性书写一个单元测试
- 所有的测试类必须以Test开头
- 所有的测试方法名必须为public类型并且以test开头
- 所有的测试类全部放在test/main/java目录下,不可和src/main/java混放
类 org.sky.ssh.ut.BaseSpringContextCommon
package org.sky.ssh.ut; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({ "/spring/appconfig/applicationContext.xml", "/org/sky/ssh/ut/ds/datasource.xml", "/spring/hibernate/hibernate.xml" }) public class BaseSpringContextCommon { }
org.sky.ssh.ut.ds.datasource.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> <!-- configure data base connection pool by using JNDI --> <!-- <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>${jdbc.jndiname}</value> </property> </bean> --> <!-- configure data base connection pool by using C3P0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}" /> <property name="jdbcUrl" value="${jdbc.databaseURL}" /> <property name="user" value="alpha_test" /> <property name="password" value="password_1" /> <property name="initialPoolSize" value="10" /> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="15" /> <property name="acquireIncrement" value="1" /> <property name="maxIdleTime" value="5" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="submit*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> <tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> <tx:method name="upd*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> <tx:method name="query*" read-only="true" /> <tx:method name="find*" read-only="true" /> <tx:method name="get*" read-only="true" /> <tx:method name="view*" read-only="true" /> <tx:method name="search*" read-only="true" /> <tx:method name="check*" read-only="true" /> <tx:method name="is*" read-only="true" /> <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="serviceMethod" expression="execution(* org.sky.ssh.service.impl.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" /> </aop:config> </beans>
<property name="password" value="password_1" />
我们先来书写一个单元测试类吧
org.sky.ssh.ut.TestLoginDAO
package org.sky.ssh.ut; import static org.junit.Assert.assertEquals; import javax.annotation.Resource; import org.junit.Test; import org.sky.ssh.dao.LoginDAO; import org.springframework.test.annotation.Rollback; public class TestLoginDAO extends BaseSpringContextCommon { @Resource private LoginDAO loginDAO; @Test @Rollback(false) public void testLoginDAO() throws Exception { String loginId = "alpha"; String loginPwd = "aaaaaa"; long answer = loginDAO.validLogin(loginId, loginPwd); assertEquals(1, answer); } }
很简单吧,把原来的LongDAO注入进我们的单元测试类中,然后在test方法前加入一个@Test代码该方法为“单元测试”方法即可被junit可识别,然后我们调用一下LoginDAO中的.validLogin方法,测试一下返回值。
运行方法为:
在eclipse打开该类的情况下右键->run as Junit Test
然后选junit4来运行,运行后直接出错抛出:
Class not found org.sky.ssh.ut.TestLoginDAO java.lang.ClassNotFoundException: org.sky.ssh.ut.TestLoginDAO at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:247) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.loadClass(RemoteTestRunner.java:693) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.loadClasses(RemoteTestRunner.java:429) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
这样一个错误,为什么?
其原因在于我们的工程是在eclipse里使用的m2 eclipse这个插件生成的,因此在做单元测试时由于我们的unit test的类是放在test/main/java这个目录下,而这个目录是我们手工建的,因此eclipse不知道这个目录的对应的编译输出的class的目录了.
没关系,按照下面的方法:
右键->选择run as->run configuration,打开如下的设置
选择classpath这个选项栏
- 单击user Entries
- 单击Advanced按钮
- 在弹出框中选择Add Folders
- 点ok按钮
3.2 结合dbunit来做单元测试
我们有了junit为什么还要引入一个dbunit呢?这不是多此一举吗?
试想一下下列场景:
我们开发时连的是开发用的数据库,一张表里有一堆的数据,有些数据不是自己的插的是其它的开发人员插的,那么我想要测试一个dao或者是service方法,获得一个List,然后判断这个List里的值是否为我想要的时候,有可能会碰到下属这样的情况:
运行我的service或者dao方法得到一个list,该list含有6个值,但正好在运行时另一个开发人员因为测试需要往数据库里又插了一些值,导致我的测试方法失败,对不对,这种情况是有可能的。
怎么办呢?比较好的做法是我们需要准备一份自己的业务数据即prepare data,因为是我们自己准备的数据数据,因此它在经过这个方法运行后得到的值,这个得到的值是要经过一系列的业务逻辑的是吧?因此这个得到的值即:expected data是可以被精确预料的。
因此,我们拿着这个expected data与运行了我们的业务方法后得到的结果进行比对,如果比对结果一致,则一定是测试成功,否则失败,对吧?
这就是我们常说的,测试用数据需要是一份干净的数据。
那么为了保持我们的数据干净,我们在测试前清空我们的业务表,插入数据,运行测试地,比对结果,删除数据(也可以不删除,因为每次运行时都会清空相关的业务表),这也就是为什么我们事先要专门搞一个数据库或者是数据库实例,在运行单元测试时我们的数据库连接需要指向到这个单元测试专用的数据库的原因了,见下面的测试流程表:
有了DbUnit,它就可以帮助我们封装:
- 准备测试用数据
- 清空相关业务表
- 插入测试数据
- 比对结果
- 清除先前插入的业务数据
3.3 构建spring+junit+dbunit的框架
org.sky.ssh.ut.util.CleanTableXmlAdapter
package org.sky.ssh.ut.util; import org.dom4j.Element; import org.dom4j.VisitorSupport; import java.util.*; public class CleanTableXmlAdapter extends VisitorSupport { private ArrayList tableList = new ArrayList(); public CleanTableXmlAdapter() { } public void visit(Element node) { try { if ((node.getName().toLowerCase()).equals("table")) { TableBean tBean = new TableBean(); tBean.setTableName(node.getText()); tableList.add(tBean); } } catch (Exception e) { } } public ArrayList getTablesList() { if (tableList == null || tableList.size() < 1) { return null; } else { return tableList; } } }
org.sky.ssh.ut.util.TableBean
package org.sky.ssh.ut.util; import java.io.*; public class TableBean implements Serializable{ private String tableName = ""; public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } }
org.sky.ssh.ut.util.XmlUtil
package org.sky.ssh.ut.util; import java.util.*; import java.io.*; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.VisitorSupport; import org.dom4j.io.SAXReader; import org.springframework.core.io.ClassPathResource; public class XmlUtil { public ArrayList getCleanTables(String xmlFile) { ArrayList tablesList = new ArrayList(); try { SAXReader reader = new SAXReader(); File file = new File(xmlFile); Document doc = reader.read(file); CleanTableXmlAdapter xmlAdapter = new CleanTableXmlAdapter(); doc.accept(xmlAdapter); tablesList = xmlAdapter.getTablesList(); return tablesList; } catch (Exception e) { e.printStackTrace(); return null; } } }
3.4使用框架
test_del_table.xml文件
<?xml version="1.0" encoding="UTF-8"?> <Tables> <table>t_student</table> </Tables>
test_insert_table.xml文件
<?xml version="1.0" encoding="UTF-8"?> <dataset> <t_student student_no="101" student_name="alice"/> <t_student student_no="102" student_name="jil"/> <t_student student_no="103" student_name="leon"/> <t_student student_no="104" student_name="chris"/> <t_student student_no="105" student_name="Ada Wong"/> </dataset>
测试类org.sky.ssh.ut.TestStudentService
package org.sky.ssh.ut; import static org.junit.Assert.assertEquals; import java.io.File; import java.io.FileInputStream; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.annotation.Resource; import javax.sql.DataSource; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.DefaultDataSet; import org.dbunit.dataset.DefaultTable; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.dbunit.ext.mysql.MySqlDataTypeFactory; import org.dbunit.operation.DatabaseOperation; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.sky.ssh.service.StudentService; import org.sky.ssh.ut.util.TableBean; import org.sky.ssh.ut.util.XmlUtil; import org.sky.ssh.vo.StudentVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.test.annotation.Rollback; public class TestStudentService extends BaseSpringContextCommon { private final static String INSERT_TBL = "org/sky/ssh/ut/xmldata/student/test_insert_table.xml"; private final static String DEL_TBL = "org/sky/ssh/ut/xmldata/student/test_del_table.xml"; @Autowired private DataSource dataSource; @Resource private StudentService stdService; @SuppressWarnings("deprecation") @Before public void setUp() throws Exception { IDatabaseConnection connection = null; try { connection = new DatabaseConnection(DataSourceUtils.getConnection(dataSource)); DatabaseConfig config = connection.getConfig(); config.setProperty("http://www.dbunit.org/properties/datatypeFactory", new MySqlDataTypeFactory()); //trunkTables(connection); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); URL url = classLoader.getResource(INSERT_TBL); if (url == null) { classLoader = ClassLoader.getSystemClassLoader(); url = classLoader.getResource(INSERT_TBL); } IDataSet dateSetInsert = new FlatXmlDataSetBuilder().build(new FileInputStream(url.getFile())); DatabaseOperation.CLEAN_INSERT.execute(connection, dateSetInsert); } catch (Exception e) { e.printStackTrace(); throw e; } finally { if (connection != null) { connection.close(); } } } @After public void tearDown() throws Exception { IDatabaseConnection connection = null; try { connection = new DatabaseConnection(DataSourceUtils.getConnection(dataSource)); DatabaseConfig config = connection.getConfig(); config.setProperty("http://www.dbunit.org/properties/datatypeFactory", new MySqlDataTypeFactory()); //trunkTables(connection); } catch (Exception e) { e.printStackTrace(); throw e; } finally { if (connection != null) { connection.close(); } } } private void trunkTables(IDatabaseConnection connection) throws Exception { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); URL url = classLoader.getResource(DEL_TBL); if (url == null) { classLoader = ClassLoader.getSystemClassLoader(); url = classLoader.getResource(DEL_TBL); } XmlUtil xmlUtil = new XmlUtil(); List tablesList = xmlUtil.getCleanTables(url.getFile()); Iterator it = tablesList.iterator(); while (it.hasNext()) { TableBean tBean = (TableBean) it.next(); IDataSet dataSetDel = new DefaultDataSet(new DefaultTable(tBean.getTableName())); DatabaseOperation.DELETE_ALL.execute(connection, dataSetDel); } } @Test @Rollback(false) public void testGetAllStudent() throws Exception { List<StudentVO> stdList = new ArrayList<StudentVO>(); stdList = stdService.getAllStudent(); assertEquals(5, stdList.size()); } }
- 该测试方法每次都清空t_student表
- 往t_student表里注入5条数据
- 运行业务方法getAllStudent
- 比较getAllStudent方法返回的list里的size是否为5
- 清空注入的数据(也可不用去清空)
四、将ant与我们的单元测试框架连接起来并生成单元测试报告
build.properties文件
# ant appName=myssh2 webAppName=myssh2 webAppQAName=myssh2-UT local.dir=C:/eclipsespace/${appName} src.dir=${local.dir}/src/main/java test.src.dir=${local.dir}/test/main/java dist.dir=${local.dir}/dist report.dir=${local.dir}/report webroot.dir=${local.dir}/src/main/webapp lib.dir=${local.dir}/lib ext-lib.dir=${local.dir}/ext-lib classes.dir=${webroot.dir}/WEB-INF/classes resources.dir=${local.dir}/src/main/resources
build.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project name="myssh2" default="buildwar" xmlns:artifact="urn:maven-artifact-ant"> <property file="build.properties" /> <property name="classes.dir" value="${dist.dir}/${webAppName}/WEB-INF/classes" /> <path id="maven-ant-tasks.classpath" path="C:/ant/lib/maven-ant-tasks-2.1.3.jar" /> <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="urn:maven-artifact-ant" classpathref="maven-ant-tasks.classpath" /> <artifact:pom id="maven.project" file="pom.xml" /> <artifact:dependencies filesetId="deps.fileset.compile" useScope="compile"> <!--<pom file="pom.xml"/>--> <pom refid="maven.project" /> </artifact:dependencies> <path id="compile.classpath"> <fileset dir="${lib.dir}"> <include name="*.jar" /> </fileset> </path> <target name="clean" description="Delete old build and dist directories"> <delete dir="${dist.dir}" /> <delete dir="${report.dir}" /> <mkdir dir="${report.dir}" /> <mkdir dir="${dist.dir}" /> <!-- create war structure for production env--> <mkdir dir="${dist.dir}/${webAppName}" /> <mkdir dir="${dist.dir}/${webAppName}/WEB-INF" /> <mkdir dir="${dist.dir}/${webAppName}/WEB-INF/lib" /> <mkdir dir="${dist.dir}/${webAppName}/WEB-INF/classes" /> <mkdir dir="${dist.dir}/${webAppName}/css" /> <mkdir dir="${dist.dir}/${webAppName}/images" /> <mkdir dir="${dist.dir}/${webAppName}/jsp" /> <!-- create war structure for qa env --> <mkdir dir="${dist.dir}/${webAppQAName}" /> <mkdir dir="${dist.dir}/${webAppQAName}/WEB-INF" /> <mkdir dir="${dist.dir}/${webAppQAName}/WEB-INF/lib" /> <mkdir dir="${dist.dir}/${webAppQAName}/WEB-INF/classes" /> <mkdir dir="${dist.dir}/${webAppQAName}/WEB-INF/classes/org/sky/ssh/ut/ds" /> <mkdir dir="${dist.dir}/${webAppQAName}/WEB-INF/classes/org/sky/ssh/ut/xmldata/student" /> <mkdir dir="${dist.dir}/${webAppQAName}/css" /> <mkdir dir="${dist.dir}/${webAppQAName}/images" /> <mkdir dir="${dist.dir}/${webAppQAName}/jsp" /> </target> <target name="download-libs" depends="clean"> <copy todir="${lib.dir}"> <fileset refid="deps.fileset.compile" /> <mapper type="flatten" /> </copy> </target> <target name="compile" description="Compile java sources" depends="download-libs"> <!-- compile main class --> <javac debug="true" destdir="${dist.dir}/${webAppName}/WEB-INF/classes" includeAntRuntime="false" srcdir="${src.dir}"> <classpath refid="compile.classpath" /> </javac> <copy todir="${dist.dir}/${webAppName}/WEB-INF/lib"> <fileset dir="${lib.dir}"> <include name="*.jar" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppName}/WEB-INF/classes"> <fileset dir="${resources.dir}"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppName}/css"> <fileset dir="${webroot.dir}/css"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppName}/images"> <fileset dir="${webroot.dir}/images"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppName}/jsp"> <fileset dir="${webroot.dir}/jsp"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppName}"> <fileset dir="${webroot.dir}"> <include name="*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppName}/WEB-INF"> <fileset dir="${webroot.dir}/WEB-INF"> <include name="*.*" /> </fileset> </copy> </target> <target name="compileQA" description="Compile java sources" depends="compile"> <!-- compile main class --> <javac debug="true" destdir="${dist.dir}/${webAppQAName}/WEB-INF/classes" includeAntRuntime="false" srcdir="${src.dir}"> <classpath refid="compile.classpath" /> </javac> <javac debug="true" destdir="${dist.dir}/${webAppQAName}/WEB-INF/classes" includeAntRuntime="false" srcdir="${test.src.dir}"> <classpath refid="compile.classpath" /> </javac> <copy todir="${dist.dir}/${webAppQAName}/WEB-INF/lib"> <fileset dir="${lib.dir}"> <include name="*.jar" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/WEB-INF/classes"> <fileset dir="${resources.dir}"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/css"> <fileset dir="${webroot.dir}/css"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/images"> <fileset dir="${webroot.dir}/images"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/jsp"> <fileset dir="${webroot.dir}/jsp"> <include name="**/*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}"> <fileset dir="${webroot.dir}"> <include name="*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/WEB-INF"> <fileset dir="${webroot.dir}/WEB-INF"> <include name="*.*" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/WEB-INF/classes/org/sky/ssh/ut/ds"> <fileset dir="${test.src.dir}/org/sky/ssh/ut/ds"> <include name="*.xml" /> </fileset> </copy> <copy todir="${dist.dir}/${webAppQAName}/WEB-INF/classes/org/sky/ssh/ut/xmldata/student"> <fileset dir="${test.src.dir}/org/sky/ssh/ut/xmldata/student"> <include name="*.xml" /> </fileset> </copy> <antcall target="junitreport"> </antcall> </target> <target name="buildwar" depends="compileQA"> <war warfile="${dist.dir}/${webAppName}.war"> <fileset dir="${dist.dir}/${webAppName}" /> </war> </target> <target name="junitreport"> <junit printsummary="on" haltonfailure="false" failureproperty="tests.failed" showoutput="true"> <classpath> <pathelement path="${dist.dir}/${webAppQAName}/WEB-INF/classes" /> <fileset dir="${lib.dir}"> <include name="*.jar" /> </fileset> <fileset dir="${ext-lib.dir}"> <include name="*.jar" /> </fileset> </classpath> <formatter type="xml" /> <batchtest todir="${report.dir}"> <fileset dir="${dist.dir}/${webAppQAName}/WEB-INF/classes"> <include name="org/sky/ssh/ut/Test*.*" /> </fileset> </batchtest> </junit> <junitreport todir="${report.dir}"> <fileset dir="${report.dir}"> <include name="TEST-*.xml" /> </fileset> <report format="frames" todir="report" /> </junitreport> <fail if="tests.failed"> --------------------------------------------------------- One or more tests failed, check the report for detail... --------------------------------------------------------- </fail> </target> </project>
对照着上面的build的流程图,很容易看懂