通向架构师的道路(第二十五天)SSH的单元测试与dbunit的整合

一、前言

在二十三天中我们介绍了使用maven来下载工程的依赖库文件,用ant来进行war包的建立。今天我们在这个基础上将使用junit+dbunit来进行带有单元测试报告的框架的架构。

目标:

  1. 每次打包之前自动进行单元测试并生成单元测试报告
  2. 生成要布署的打包文件即war包
  3. 单元测试的代码不能够被打在正式的要布署的war包内,单元测试仅用于unit test用
  4. 使用模拟数据对dao层进行测试,使得dao方法的测试结果可被预料

二、Junit+Ant生成的单元测试报告

上面是一份junit生成的测试报告,它可以与ant任务一起运行然后自动生成这么一份html的测试报告,要生成这样的一份junit test report我们需要调用ant任务中的<junitreport>这个task,示例代码如下:

<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,因此我们每天的打包要干的事情应该是:

  1. 程序员check in代码时必须把相关的unit test也check in源码服务器
  2. 次日的零晨由持续集成构件如:cruisecontrol自动根据设好的schedule把所有的源码服务器的代码进行编译
  3. 运行单元测试
  4. 生成报告
  5. 打包布署到QA服务器上去

如果考究点的还会生成一份“单元测试覆盖率”报告。

那么有了这样的单元测试报告,项目组组长每天早上一上班检查一下单元测试报告就知道昨天代码check in的情况,有多少是成功多少是失败,它们分别是哪些类,哪些方法,以找到相关的负责人。

同时,有了单元测试报告,如果测试报告上显示的是有fail的地方,该版本就应被视之为fail,不能被送给QA进行进一步的测试,直到所有的单元测试成功才能被送交QA。

三、如何在Spring下书写一个单元测试方法

3.1使用spring的注入特性书写一个单元测试

Spring是一个好东西,一切依赖注入,连单元测试都变成了依赖注入了,这省去我们很多麻烦。

我们可以将web工程中的applicationContext、Datasource甚至iBatis或者是Hibernate的配署都可以注入给junit,这样使得我们可以用IoC的方法来书写我们的单元测试类。

此处,我们使用的junit为4.7, 而相关的spring-test库文件为3.1,我都已经在pom.xml文件中注明了.

我们先在eclipse里建立一个专门用来放单元测试类的src folder:test/main/java。

注意一下单元测试类的coding convention:

  • 所有的测试类必须以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 {
}

该类为一个基类,我们所有的单元测试类全部需要继承自该类,大家可以把这个类认为一个spring的context加载器,注意这边的datasource.xml。

因为我们在做测试方法时势必会涉及到对一些数据进行操作,因此我们在数据库里除了平时开发和布署用的数据库外,还有一个专门用于运行“单元测试”的“单元测试数据库”或者“单元测试数据库实例”,因此我们在单元测试时会把我们当前的数据库连接“硬”指向到“单元测试用数据库”上去.

这个datasource.xml文件位于/org/sky/ssh/ut/ds目录下,见下图(当然它也必须被放在test/main/java目录里哦:

该文件内容如下:

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="user" value="alpha_test" />

<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这个选项栏

  1. 单击user Entries
  2. 单击Advanced按钮
  3. 在弹出框中选择Add Folders
  4. 点ok按钮

在下一个弹出框中选择我们的junit test的源码在被编译后输出的目录即myssh2工程的WebContent/WEB-INF/classes目录,对吧。

点OK按钮

点Apply按钮

点Run按钮,查看运行效果

运行成功,说明该unit test书写的是对的。

3.2 结合dbunit来做单元测试

我们有了junit为什么还要引入一个dbunit呢?这不是多此一举吗?

试想一下下列场景:

我们开发时连的是开发用的数据库,一张表里有一堆的数据,有些数据不是自己的插的是其它的开发人员插的,那么我想要测试一个dao或者是service方法,获得一个List,然后判断这个List里的值是否为我想要的时候,有可能会碰到下属这样的情况:

运行我的service或者dao方法得到一个list,该list含有6个值,但正好在运行时另一个开发人员因为测试需要往数据库里又插了一些值,导致我的测试方法失败,对不对,这种情况是有可能的。

怎么办呢?比较好的做法是我们需要准备一份自己的业务数据即prepare data,因为是我们自己准备的数据数据,因此它在经过这个方法运行后得到的值,这个得到的值是要经过一系列的业务逻辑的是吧?因此这个得到的值即:expected data是可以被精确预料的。

因此,我们拿着这个expected data与运行了我们的业务方法后得到的结果进行比对,如果比对结果一致,则一定是测试成功,否则失败,对吧?

这就是我们常说的,测试用数据需要是一份干净的数据

那么为了保持我们的数据干净,我们在测试前清空我们的业务表,插入数据,运行测试地,比对结果,删除数据(也可以不删除,因为每次运行时都会清空相关的业务表),这也就是为什么我们事先要专门搞一个数据库或者是数据库实例,在运行单元测试时我们的数据库连接需要指向到这个单元测试专用的数据库的原因了,见下面的测试流程表:

有了DbUnit,它就可以帮助我们封装:

  • 准备测试用数据
  • 清空相关业务表
  • 插入测试数据
  • 比对结果
  • 清除先前插入的业务数据

这一系列底层的操作。

现在我们可以开始搭建我们的单元测试框架了,下面是这个单元测试框架的”逻辑表达图“(一个架构设计文档不仅需要有logic view还要有physical view。。。当然还有更多,以后会一点点分享出来)

这边的Session Factory是结合的原有框架的Hibernate的Session Factory,我们也可以把它改成iBatis,Jdbc Template等等等。。。它可以稍作变动就可适用于一切SSX这样的架构。

该框架的优点如下:

3.3 构建spring+junit+dbunit的框架

除去上述的一些类和配置我们还需要3个基类,它们分别位于test/main/java目录下(因为它们都属于unit test对吧)

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());
	}
}
  1. 该测试方法每次都清空t_student表
  2. 往t_student表里注入5条数据
  3. 运行业务方法getAllStudent
  4. 比较getAllStudent方法返回的list里的size是否为5
  5. 清空注入的数据(也可不用去清空)

然后我们在eclipse里用junit来运行我们这个测试类吧。

我们现在用我们的单元测试用数据库帐号连入我们的数据库,查询t_student表

我们往该表中手动插入一条数据

再重新运行一遍我们的单元测试

测试结果还是成功,再重新连入我们单元测试用数据库实例查询t_student表,发觉还是5条记录,说明我们的框架达到了我们的目标。

四、将ant与我们的单元测试框架连接起来并生成单元测试报告

先来看一下我们的nightly building,即每天次日的零晨将要生成的单元测试与打包布署的流程吧

(需要ant1.8及以上版本运行)


然后下面给出build.xml文件(需要ant1.8及以上版本运行)(结合了maven的依赖库机制)

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的流程图,很容易看懂

打开一个command窗口,进入到我们的工程的根目录下,设置好ANT_HOME并将%ANT_HOME%\bin目录加入到path中去,然后在工程的根据目录下运行ant,就能看到打包和运行unit test的效果了。

build完后可以在工程的根目录下找到一个report目录,打开后里面有一堆的html文件

双击index.htm这个文件查看单元测试报告

我们在windows的资源管理器中打开我们的工程,在根目录下有一个dist目录,打开这个目录,我们会开到两个目录与一个.war文件,它们分别是:

其中myssh2-UT是专门用来run unit test的,而myssh2是可以用于发布到production environment的,我们打开myssh2.war这个包,我们可以看到,由于这个是正确布署的war,因此里面是不能够含有unit test的相关类与方法的,完全按照上述的打包流程图来做的。

结束今天的教程!!!

时间: 2024-07-28 16:13:37

通向架构师的道路(第二十五天)SSH的单元测试与dbunit的整合的相关文章

通向架构师的道路 第二十五天 SSH的单元测试与dbunit的整合(下)

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&quo

通向架构师的道路 第二十六天 漫谈架构与设计文档的写作技巧

前言: 这篇是一篇番外篇,没有太多代码与逻辑,完全是一种"软"技巧,但是它对于你如何成为一名合构的架构设 计人员很重要. 在此要澄清一点,架构师本身也是"程序员",不是光动嘴皮子的家伙们,如果你不是一名程序虽出身 那你根本谈不上也不可能成为一名架构师. 那么架构师还有哪些是作为一名程序员来说不具备的呢? 其中有一项 能力就叫做"文档写作能力". 一.Soft Skill与Hard Skill 作为一名架构师除了是一名资深的程序员外,它还 必须具有

通向架构师的道路 第十五天 IBM Websphere的安装与优化 (二)

5.3 在WAS内布署应用 一般我们使用ear格式在WAS内布署我们的web应用,因此此处和weblogic, tomcat稍稍有点不一样. 为此,我们做了一个ant脚本用于打包我们的ear. 一个ear文件的格式应该如下: myEAR |__ META-INF    |__application.xml |__myWAR.war 可以看到,一个ear文件: 包含一个META-INF目录,在该目录下会有一个application.xml文件. 然后和META-INF目录同级的地方会有一个.war

通向架构师的道路 第二十四天 Oracle性能调优(下)

4.4 Oracle表空间管理 Oracle的表空间文件都放在$ORACLE_HOME/oradata如/opt/oracle/product/10/oradata这样的目录中的 Oracle的表空间支持"热插拨" 即在Oracle运行时发觉表空间不够时可以直接打开Oracle的管理界面来动态给它划一块硬盘空间,或者甚至你又装了一块硬 盘进服务后,Oracle可以把表空间在运行时扩展到新插入的磁盘中.

通向架构师的道路 第二十四天 Oracle性能调优(上)

前言 这次,我们将在Linux下来动手完成Oracle数据库的安装与使用. Oracle本身是可以免费下载的包括 它 的企业版以及被它收购的Weblogic和Sun中的几乎任何东西你都可以拿来下载和使用,不像IBM和Tibco一些其它厂商,只有"试 用版"给你下载,Oracle的东西没有时间限制,你拿来做练习,搭实验环境都是没有任何的问题的. 但是,如果你出了 问题,需要用到Oracle的补丁或者是Oracle的技术支持,这就开始收费. Oracle就是这种"卖Service

通向架构师的道路 第十五天 IBM Websphere的安装与优化 (一)

一.IBMWebsphere 02年开始接触EJB1.x时,当时有一本巨肥厚无比的书叫作"ejb从入门到精通(master ejb2.0)",红皮的,wrox公司出版的. 该书带有1张光盘,光盘里有3个App Server. 1. Jboss2.2.1 2. Weblogic6.1 3. IBM Websphere ApplicationServer5.x 从那时起开始知道,哦,原来这3大厂商是做j2ee容器的.果然,时至今日这三大容器还是处于世界上无可争议的地位. 因此,我们前面讲了

通向架构师的道路 第二十七天 IBM网格计算与企业批处理任务架构

一.批处理 我们在一些项目中如:银行.保险.零商业门店系统中的对帐.结帐.核算.日结等操作中经常会碰到一 些"批处理"作业. 这些批处理经常会涉及到一些大数据处理,同时处理一批增.删.改.查等SQL,往往涉及到好 几张表,这边取点数据那边写点数据,运行一些存储过程等. 批处理往往耗时.耗资源,往往还会用到多线程去设计程 序代码,有时处理不好还会碰到内存泄漏.溢出.不够.CPU占用高达99%,服务器被严重堵塞等现象. 笔者曾经经历过 一个批处理的3次优化,该批处理笔者按照数据库连接池的原

通向架构师的道路 第二十二天 万能框架spring(四) 使用struts2 下

6. myssh2工程的完整pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&qu

通向架构师的道路(第五天)之tomcat集群-群猫乱舞

一.为何要集群 单台App Server再强劲,也有其瓶劲,先来看一下下面这个真实的场景. 当时这个工程是这样的,tomcat这一段被称为web zone,里面用spring+ws,还装了一个jboss的规则引擎Guvnor5.x,全部是ws没有service layer也没有dao layer. 然后App Zone这边是weblogic,传输用的是spring rmi,然后App Zone这块全部是service layer, dao layer和数据库打交道. 用户这边用的是.net,以w

通向架构师的道路 第二十三天 maven与ant的奇妙整合

一.前言 我们在<万能框架spring>前四天中都用到了maven,接下去要讲述在SSX这样的架构下我们的"单元测试" 是怎么进行的,但是在此之前我们再来深入入解一下maven,因为我们的单元测试需要用到的是junit+ant+junitreport这样的组 合.而......由于我们已经使用了maven,那么我们如何可以延续经典的junit+ant这样的单元测试的组合呢?其答案就是 把maven和ant再进行组合一下. 二.用Maven任务在Ant中使用Maven依赖 M