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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 一、前言在二十三天中我们介绍了使用maven来下载工程的依赖库文件,用ant来进行war包的建立。今天我们在这个基础上将使用junit+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的相关类与方法的,完全按照上述的打包流程图来做的。

结束今天的教程!!!

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
数据采集 机器学习/深度学习 大数据
行为检测代码(一):超详细介绍C3D架构训练+测试步骤
这篇文章详细介绍了C3D架构在行为检测领域的应用,包括训练和测试步骤,使用UCF101数据集进行演示。
45 1
行为检测代码(一):超详细介绍C3D架构训练+测试步骤
|
6月前
|
人工智能 缓存 并行计算
技术改变AI发展:Ada Lovelace架构解读及RTX 4090性能测试分析(系列三)
简介:随着人工智能(AI)的迅速发展,越来越多的应用需要巨大的GPU计算资源。Ada lovelace(后面简称Ada)是NVIDIA最新的图形处理器架构,随2022年9月20日发布的RTX 4090一起公布。
138130 62
技术改变AI发展:Ada Lovelace架构解读及RTX 4090性能测试分析(系列三)
|
缓存 测试技术 数据中心
【计算机架构】计算 CPU 动态功耗 | 集成电路成本 | SPEC 基准测试 | Amdahl 定律 | MIPS 性能指标
【计算机架构】计算 CPU 动态功耗 | 集成电路成本 | SPEC 基准测试 | Amdahl 定律 | MIPS 性能指标
455 0
|
1月前
|
存储 消息中间件 运维
架构升级的救星!流量回放自动化测试的必备指南
大家好,我是小米,一名29岁的技术宅。今天分享一个物联网领域的实用技能——流量回放自动化测试。系统重构后,测试工作量巨大,本文介绍如何通过日志收集和数据回放进行自动化测试,包括离线、实时和并行回放模式,帮助快速定位Bug,提升测试效率和系统稳定性。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
46 3
|
3月前
|
缓存 Java Maven
SpringCloud基于Eureka的服务治理架构搭建与测试:从服务提供者到消费者的完整流程
Spring Cloud微服务框架中的Eureka是一个用于服务发现和注册的基础组件,它基于RESTful风格,为微服务架构提供了关键的服务注册与发现功能。以下是对Eureka的详细解析和搭建举例。
|
4月前
|
测试技术 调度 微服务
微服务架构下的两类测试
【7月更文挑战第16天】微服务架构下的两类测试:流量录制回放测试和仿真环境测试
|
5月前
|
存储 测试技术 数据库
在django中的测试架构
【6月更文挑战第13天】该文主要讨论Django项目的测试数据查询和测试架构。文中展示了如何创建`TestCase`子类进行测试,并提供了执行测试的不同选项,如增加详细信息、并行运行和选择性运行特定测试。
48 2
在django中的测试架构
|
3月前
|
运维 Kubernetes 索引
揭秘ChaosBlade的Helm安装双架构:一步到位,让系统故障测试变得前所未有的简单和高效!
【8月更文挑战第7天】在多变的IT环境中,确保应用的稳定与可用至关重要。混沌工程通过故意引入故障来增强系统韧性。ChaosBlade是一款开源混沌实验工具,支持多样化的故障注入。结合Kubernetes的包管理器Helm,可简化ChaosBlade在集群中的部署。本文介绍如何使用Helm安装ChaosBlade双架构版本,包括添加仓库、选择版本、安装配置及验证等步骤,助力高效实施混沌工程,提升系统稳定性和可靠性。
57 0
|
4月前
|
监控 供应链 jenkins
「架构」单元测试及运用
TechCorp在软件项目中重视单元测试,结合静态代码分析(如SonarQube)与动态的白盒测试(使用JUnit和JaCoCo)。通过设定代码覆盖率标准和自动化回归测试(集成到CI/CD),团队确保了代码质量、减少了缺陷,加快了开发速度,打造出稳定、高性能的电商平台。单元测试成为提升软件质量和开发效率的关键实践。
40 0
|
6月前
|
测试技术 Python
Python测试架构unittest
【4月更文挑战第19天】
27 3
下一篇
无影云桌面