在二十三天中我们介绍了使用maven来下载工程的依赖库文件,用ant来进行war包的建立。今天我们在这个基础上将使用junit+dbunit来进行带有单元测试报告的框架的架构。
目标:
<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>
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 { }
<?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>
我们先来书写一个单元测试类吧
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); } }
运行方法为:
在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这个选项栏
我们有了junit为什么还要引入一个dbunit呢?这不是多此一举吗?
试想一下下列场景:
我们开发时连的是开发用的数据库,一张表里有一堆的数据,有些数据不是自己的插的是其它的开发人员插的,那么我想要测试一个dao或者是service方法,获得一个List,然后判断这个List里的值是否为我想要的时候,有可能会碰到下属这样的情况:
运行我的service或者dao方法得到一个list,该list含有6个值,但正好在运行时另一个开发人员因为测试需要往数据库里又插了一些值,导致我的测试方法失败,对不对,这种情况是有可能的。
怎么办呢?比较好的做法是我们需要准备一份自己的业务数据即prepare data,因为是我们自己准备的数据数据,因此它在经过这个方法运行后得到的值,这个得到的值是要经过一系列的业务逻辑的是吧?因此这个得到的值即:expected data是可以被精确预料的。
因此,我们拿着这个expected data与运行了我们的业务方法后得到的结果进行比对,如果比对结果一致,则一定是测试成功,否则失败,对吧?
这就是我们常说的,测试用数据需要是一份干净的数据。
那么为了保持我们的数据干净,我们在测试前清空我们的业务表,插入数据,运行测试地,比对结果,删除数据(也可以不删除,因为每次运行时都会清空相关的业务表),这也就是为什么我们事先要专门搞一个数据库或者是数据库实例,在运行单元测试时我们的数据库连接需要指向到这个单元测试专用的数据库的原因了,见下面的测试流程表:
有了DbUnit,它就可以帮助我们封装:
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; } } }
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; } }
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; } } }
<?xml version="1.0" encoding="UTF-8"?> <Tables> <table>t_student</table> </Tables>
<?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>
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()); } }
# 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
<?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>