大家可能对如下情景比较熟悉:
如果开发过SSH的web项目,启动服务器可能会比较慢,有的项目甚至需要1分多钟,甚至更多,这个启动时间的等待一般就浪费了;
在开发项目时,有些功能比较复杂,当时觉得思路特清晰,但是过了一段时间后,自己也忘了,完善功能时频繁出现bug,降低开发速度;
在维护项目时,不知道自己修改的对还是不对,是否存在隐患;维护速度降下来了;
如果开发一个很多人都使用的接口,典型的如用户系统,要保证比如升级时向下兼容;
在团队间协作时,有时候只定义好接口,对方还没有给实现,如何进行同步开发?
如上问题,估计只要是个开发人员,都可能遇到过;如果此时有了单元/集成测试,那我们能很好的解决这些问题。(注:加下来如果没有特殊情况,不刻意强调 单元测试/集成测试,即提到测试是指的是单元/集成测试)
我从以下几个方面介绍测试:
1、为什么需要测试?
2、如何进行测试?
3、测试有哪些好处?
4、一切都需要测试吗?
1、为什么需要测试?
测试的目的是什么?我的理解是:
缩短发现问题到解决问题的速度;
给程序一个修改后能验证是否正确的保证;(回归测试)
如果是开源软件,我们可以通过单元测试了解其是怎么使用的;比如我之前通过cglib的单元测试学习过cglib的使用;
所以如果你遇到如上问题,就需要写测试。写测试可能是为了自己(1、2);也可能是为了帮助别人(3)。
2、如何进行测试?
很多朋友不知道如何进行测试,其实测试很简单,别把它想复杂了,按照自己的想法测试每个功能点是否正确即可。
21、测试流程
单元测试流程
集成测试流程
集成测试流程
可以看出,单元测试与集成测试唯一不同点是一个调用依赖系统而一个不调用;因为单元测试是最小粒度的测试,如在Java中是测试一个类,不会测试依赖系统;而集成测试是会测试依赖系统的。
测试的步骤:
准备环境
调用被测系统
验证
清理环境
环境:也叫做夹具(fixture)或者固件,表示调用被测系统时需要准备/清理的数据等等;
被测系统:在Java中就是要测试的类,如UserService;
依赖系统:测试被测系统时,其依赖的部分,如UserDao;
测试用例:包含测试方法的类,里边有很多测试方法来测试被测系统。
接下来仔细看看各部分都做了哪些工作。
22、环境
环境,也叫做夹具(fixture),表示调用被测系统时需要准备/清理的数据等等;保证测试时环境是干净的,如不被之前的数据库数据影响;保证每次测试都是在干净/新鲜的环境中执行的。所谓干净的环境表示如当前测试不被之前测试插入/删除/修改的数据造成影响。在junit中可以使用:
@Before(setUp) 安装夹具或准备环境:在测试用例的每个测试方法之前执行;比如创建新鲜的被测系统,单元测试时安装Mock的依赖系统;
@After(tearDown)卸载夹具或清理环境:在测试用例的每个测试方法之后执行;比如数据库测试时回滚事务,删除数据;关闭文件;
@BeforeClass:在整个测试用例之前执行;
@AfterClass:在整个测试用例之后执行;
使用如上方法,而不是直接在测试方法中安装/卸载;是因为不管有没有异常,@After/@AfterClass都会执行,这样防止出现异常可能造成环境是不新鲜的问题。
如果大家使用spring test来测试数据库相关的系统,可以考虑使用@TransactionConfiguration来支持默认事务回滚,这样不会对现有系统造成影响。具体可参考《第十三章 测试 之 131 概述 132 单元测试 ——跟我学spring3》和《第十三章 测试 之 133 集成测试 ——跟我学spring3》
测试时一定要保证环境是干净/新鲜的,才能保证每次测试的结果是一样的。
23、被测系统与依赖系统
被测系统:在Java中就是被测试的Java类。
依赖系统:就是被测试Java类依赖的其他类。
如果是单元测试,一般情况下,会对依赖系统进行模拟(Mock),即给它一个假的实现;典型的如测试服务层时注入一个Mock的DAO层,这样的好处:
加快测试速度;因为不会调用真实的被测系统,所以速度特别快;
测试还没有完成的功能;尤其在多团队协作时,可以只在定义好接口的情况下开发系统;
如果是集成测试时,直接注入真实的依赖系统即可,好处:
完成联调;
发现自己的问题;
还可能发现自己使用上问题及使用的API的问题;
单元测试虽然好,但是是隔离测试,即不会调用被测系统来完成测试,因为不是真实的联调,所以很可能会潜在有一些问题,因此还是需要集成测试。(所以不是很刻意分单元或集成测试,且有些系统可能只有集成测试)
但是集成测试速度是比较慢的,一般提交给CI执行,不影响当前开发进度。
24、验证
验证的目的:是保证实际结果和我们预期的结果是否一致,说白了就是是否是我们想的那样。
一般使用断言来验证,如:
AssertassertEquals(expectedResult, actualResult); //验证预期结果和实际结果是否相等
验证主要有两种:
结果验证
行为验证
结果验证:即验证被测系统返回的结果是否正确,如:
Java代码
@Test
public void testCount() {
String ql = "select count(o) from User o";
long expectedCount = repositoryHelpercount(ql) + 1;
User user = createUser();
repositoryHelpergetEntityManager()persist(user);
long acutalCount = repositoryHelpercount(ql);
AssertassertEquals(expectedCount, acutalCount);
}
验证返回的数据总数 = 插入之前的总数 + 1; 即结果验证。此处我们使用了一种叫做相对(delta)测试;即不关心数据库里到底多少条,只关心实际的和预期的差。
行为验证:即验证被测系统是否调用了依赖系统的某个API ,这个只有当我们使用Mock时测试时比较简单,如当用户注册时:
1、加积分
2、发系统消息
3、……
此时我们并不能通过结果验证是否调用了这些方法;那么我们可以使用Mock技术来完成验证是否调用了这些API,比如使用jmock测试框架就支持行为验证。集成测试是很难进行行为验证的,如果测试需要预留间谍接口。
3、测试有哪些好处?
我们写代码的目的是正确的完成某个功能,如何保证正确呢?测试!所以在不使用如单元测试技术时,我们也是需要测试,但是这个测试是我们人工验证的。缺点很明显:
不是自动的,每次需要对比预期结果与实际结果,尤其数据量/逻辑复杂时更痛苦;
不是回归的,上次测试完成后,下次还得重复自己一遍;
为了解决这个问题,我们使用如单元测试技术来解决这个问题:
测试自动化;即验证预期结果与实际结果交给计算机吧;
测试回归性,可以重复执行测试,验证修改后逻辑是否还是正确的;
即测试的好处,从如上已经提炼出来了:
缩短发现问题到解决问题的时间;
重复使用测试,保证修改后的代码还是正确的;
如果做开源项目,可以提供给使用人员参考如何使用;
因为单元测试都非常快,所以提升了开发速度;
4、一切都需要测试吗?
肯定不是,一切都是相对的;哪些不需要测试呢:
你非常熟悉的功能;
一些简单的CRUD;
你认为不需要测试的;比如你很有把握的东西,就没有必要浪费时间测试了;
哪些需要测试呢:复杂的业务逻辑/系统核心功能,最典型的如订单系统:一定要有足够的单元测试保证,这是一个电商系统的核心;还有如用户系统、积分系统等等;
框架级别/工具级别/通用级别的代码需要测试,即提供给第三方使用的代码,因为这些代码可能被很多系统依赖,应该保证其正确性;而且还要保证以后版本升级的向下兼容;
你认为需要测试的,比如你没有把握的东西,还是写点测试来缩短如开发web项目的重启系统的时间吧;
测试不是不耗时间的,没意义的测试就是浪费时间,最典型是一些书上的对一个增删改查进行测试,实际项目没有任何意义。所以你应该只对自己很难驾驭的觉得有必要的代码进行测试。不要成为一个测试狂,什么都测试。
一些测试可以参考我的《es——JavaEE快速开发脚手架》中的代码。通过测试我得到了许多好处。
到此我们介绍完成了测试,但是如果我们使用了如集成测试时,测试执行起来可能比较慢,跑一遍测试可能需要5分钟,那怎么办呢?
每天下班前跑一遍集成测试,然后修复,下班走人;
CI:持续集成,交给持续集成服务器,自动地测试完成后把测试报告以邮件的形式发到开发人员邮箱;
------------------------------------分割线----------------------------------
接下来介绍一下CI吧。
1、为什么需要CI
2、CI如何工作的
3、travis-ci介绍
1、为什么需要CI
正如前边说的,我们单独测试可能会遇到如下问题:
如果写了一个测试,就要把所有测试跑一遍看看整个系统是否是正确的,那么每次等待时间是非常漫长的;
如果团队中的其他成员改了功能并提交了,如何快速得到该次提交对当前系统代码是正确还是失败的反馈;
那怎么办呢?自动化地持续集成(CI)!CI的核心就是干这件事情的。自动化持续地集成测试。
使用CI后,如果使用Maven,可以新建多个profile:
本地测试时忽略一些比较慢的测试;
CI服务器上执行所有测试;
2、CI如何工作的
一个典型的持续集成流程:
定期检测版本服务器上是否有代码更新;
如果发现代码更新,从版本服务器下载最新的代码;
自动构建并自动化的测试;
不管错误/失败,生成报告给开发人员;
有些CI服务器还能产生可执行的软件,自动化地部署到测试机器,交给测试人员测试。
如图所示:
持续集成服务器其实就是一个定时器,自动帮你下载最新代码、编译、测试、集成及产生报告发给开发人员。
常见的CI服务器有:
Apache Continuum
Hudson
CruiseControl
Jenkins CI
TeamCity
Travis CI
我09年时使用过TeamCity社区版,足够满足常见需求;目前我使用github托管项目,使用Travis CI进行分布式的持续集成,免费,目前看来还是不错的。
3、travis-ci介绍
我现在开发的ES-JavaEE项目开发脚手架就是使用travis ci进行持续集成;具体参考《Getting started》进行与Github集成,其支持的语言:
C
C++
Clojure
Erlang
Go
Groovy
Haskell
Java
JavaScript (with Nodejs)
Objective-C
Perl
PHP
Python
Ruby
Scala
支持的数据库:
MySQL
PostgreSQL
MongoDB
CouchDB
Redis
Riak
RabbitMQ
Memcached
Cassandra
Neo4J
ElasticSearch
Kestrel
SQLite3
更多请参考其官网的介绍。
如果是Java开发人员,支持的JDK包括:OpenJDK 和 OracleJDK。 如果使用的是OpenJDK,Maven中使用ascii2native插件时,需要如下配置:
Java代码
<plugin>
<groupId>orgcodehausmojo</groupId>
<artifactId>native2ascii-maven-plugin</artifactId>
<version>10-alpha-1</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>native2ascii</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<src>src/main/messages</src>
<dest>target/${projectartifactId}/WEB-INF/classes</dest>
<includes>messagesproperties</includes>
</configuration>
</execution>
</executions>
<!-- native2ascii 使用的toolsjar -->
<dependencies>
<dependency>
<groupId>comsun</groupId>
<artifactId>tools</artifactId>
<version>170</version>
<scope>system</scope>
<systemPath>${javahome}//lib/toolsjar</systemPath>
</dependency>
</dependencies>
</plugin>
如果使用mysql,端口只能是3306。
如果想开端口测试,这是不允许的。
如下是我项目中的一个配置travisyml,放到项目的根下即可:
-----------------------------------
language: java 语言
env: 环境
- DB=mysql 使用mysql
jdk:
- openjdk jdk使用openjdk
mysql:
database: es 数据库名为es
username: root 用户名为root
password : 密码为空
encoding: utf8 编码为utf8
install: 安装时执行的脚本
- mvn install -Dmaventestskip=true mvn安装并跳过测试
before_script: script之前执行的测试
- cd web
- mvn db:create 创建数据库的mvn命令(此处使用了 maven-db-plugin 插件)
- mvn db:schema 创建脚本的mvn命令
- mvn db:data 安装数据的mvn命令
- cd
script: 测试时执行的脚步
- cd common
- mvn test 测试common子模块
- cd
- cd web
- mvn test -Pit 测试web子模块,并指定使用it profile测试(即集成测试的配置,具体参考pomxml中的profile/it)
notifications: 触发
email: 测试完成后测试报告发到哪
- zhangkaitao0503@gmailcom
-----------------------------------
持续集成不能修复代码的错误,而是和单元测试一样,缩短发现问题带解决问题的时间,这样可以提高开发效率,降低项目风险,提高项目的稳定性。而且尤其是团队协作时,可以发现其他人的代码是否对自己的代码产生影响。
到此我们利用单元测试+CI可以加速开发人员的开发速度。利用好单元测试和CI,不要纯粹为了单元测试和CI而去做这些事情。
本文没有介绍TDD,TDD并不会那么美好,我认为我们可以借鉴TDD的一些思想,但决不能迷信TDD,有时候,尤其如开发企业应用,先写功能再写测试可能效率更高,而且大部分时候是不需要TDD的。而且我也没能在实际项目中获取太多TDD的好处,但是我获得了测试的好处。
本文也没有介绍测试覆盖率,我认为不要一味的追求覆盖率,有时候有的覆盖率没有任何意义。所以不要让为了覆盖率而覆盖率拖慢了项目开发进度。
正如stackoverflow上的一篇帖子《How deep are your unit tests》上Kent Beck的回答:
写道
老板为我的代码付报酬,而不是测试,所以,我对此的价值观是——测试越少越好,少到你对你的代码质量达到了某种自信。
可以前往coolshell的“单元测试要做多细?”去得到一些经验。
运用DBUnit进行高效单元测试
译者注:最近对DBUnit比较感兴趣,看到这篇文章就翻译出来和大家共享,不过我也是New
Hand,所以翻译不好的地方请大家指正。我的MSN:zhlihui@hotmailcom,如果大家有什么好的资源和经验欢迎和我交流
引入DBUnit
现实系统中通常会有一些具有外部依赖性的对象,这些对象和数据库或者其他对象存在诸多关联。如果我们对这样的对象编写单元和组件级测试的话,可以想象将是非常麻烦的一件事因为这种外部依赖性的存在,使的我们很难将对象孤立出来进行测试。经常提及的白盒测试法,基本上就是通过控制对象的外部依赖性来达到隔离对象的目的,使的可以 *** 作这些对象的状态和相关行为。
运用 模拟对象(mock objects)
或者stubs,就是一个控制对象外部依赖性的解决方案。通过隔离那些关联的数据库访问类,象JDBC的相关 *** 作类,对于控制对象外部依赖性将是很有效的。但模拟对象的解决方案对一些特殊的应用系统架构就显得力不从心了,象那些运用了EJB的CMP(container-managed
persistence)或者 JDO(java Data
Objects)的应用系统架构,在这些架构里,数据库的访问对象是在最底层的而且很隐蔽。
由Manuel Laflamme
编写的开放源代码的DBUnit架构体系,对于控制系统内部的数据库依赖性提供了一个非常不错的解决方案。他允许程序员在整个的测试过程中自由的管理控制数据库的状态,这很重要。利用DBUnit,在测试之前,我们可以给目标数据库植入我们需要的数据集,而且,在测试完毕后,数据库完全能够回溯到测试前的状态。
在很多成功的软件项目中,测试自动化往往是关键的层面。DBUnit允许开发人员创建测试用例代码,在这些测试用例的生命周期内我们可以很好的控制数据库的状态。而且,这些测试用例是很容易实现自动化的。这样在测试过程中我们无须对它进行人工的干预,为人工干预造成的后果而担心就更没必要了。
简单介绍
配置使用DBUnit的第一步我们首先需要知道如何生成数据库schema,这个文件是XML格式的,其中包括了数据库的表及相关数据信息。
例如,这里有一个数据库表employee
,我们可以用SQL的形式这样将他表示出来。
而且,我们可以看到,一个简单的数据集可以这样表示
在DBUnit中,上面这个表和抽样数据信息可以用XML文件的形式这样表示:
<EMPLOYEE employee_uid='1'
start_date='2001-11-01'
first_name='Andrew'
ssn='xxx-xx-xxxx'
last_name='Glover' />
这个生成的XML格式的文件可以作为系统所需的所有种子文件(seed
files)的样本模版
为相互关联的测试场景创建多个种子文件是一个很有效的策略,就象通过不同的数据库文件来区分隔离数据库状态是一个道理。多种子文件策略可以将我们的测试目标锁定到较小的范围,目标数据可以只针对数据库的表,而不是整个数据库。
为了给目标数据库植入不同的职员记录,我们需要的XML数据文件如下所示:
<xml version='10' encoding='UTF-8'>
<dataset>
<EMPLOYEE employee_uid='1'
start_date='2001-01-01'
first_name='Drew' ssn='000-29-2030'
last_name='Smith' />
<EMPLOYEE employee_uid='2'
start_date='2002-04-04'
first_name='Nick' ssn='000-90-0000'
last_name='Marquiss' />
<EMPLOYEE employee_uid='3'
start_date='2003-06-03'
first_name='Jose' ssn='000-67-0000'
last_name='Whitson' />
</dataset>
现在,要让DBUnit和我们所需的数据库schema一起工作了,对于程序员来说,我们使用DBUnit进行测试可以有两种选择:通过直接编码方式进行测试或者与Ant结合
编码方式
DBUnit框架提供了一个基本的抽象测试用例类,叫做DatabaseTestCase,它是JUnit框架中的基础类TestCase的子类。如果我们使用这个类必须首先实现两个钩子方法(hook
methods):getConnection()和getDataSet()
方法getConnection()需要返回一个IDatabaseConnection类型的对象,这个对象是一个基于普通JDBC连接的包装类。例如,下面的代码段演示了在MySQL数据库环境下,IDatabaseConnection类型连接对象的创建方法。
protected IDatabaseConnection getConnection()
throws Exception {
Class driverClass = ClassforName("orggjtmmmysqlDriver");
Connection jdbcConnection = DriverManagergetConnection(
"jdbc:mysql://127001/hr", "hr", "hr");
return new DatabaseConnection(jdbcConnection);
}
方法getDataSet()返回一个IDataSet类型对象,其实,说白了,他就是我们先前提到的XML数据的种子文件的另一种表现形式。
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSet(
new
FileInputStream("hr-seedxml"));
}
有了这两个基本的方法以后,DBUnit就可以按照它预先缺省的行为工作了。DatabaseTestCase类提供了两个fixture(我叫它固件,不知仁兄同意否?)方法来控制测试前和测试后的数据库状态。这两个方法就是:
getSetUpOperation() 和 getTearDownOperation()
一种高效的实施方案就是让getSetUpOperation()方法执行REFRESH *** 作,通过这个 *** 作,我们可以用种子文件中的数据去更新目标数据库里的数据。接下来,就是getTearDownOperation(),让他去执行一个NONE *** 作,也就是什么也不执行。
protected DatabaseOperation getSetUpOperation()
throws
Exception {
return DatabaseOperationREFRESH;
}
protected DatabaseOperation getTearDownOperation()
throws
Exception {
return DatabaseOperationNONE;
}
还有一种有效的方法就是在getSetUpOperation()方法中执行CLEAN_INSERT *** 作,这样首先会将目标数据库中与我们提供的种子文件一致的数据删除,然后将我们提供的数据插入到数据库中。这个实施顺序保证了我们对数据库的精确控制。
代码样例
在一个基于J2EE的人力资源系统中,我们很希望对某个数据 *** 作周期实现测试自动化,这个 *** 作周期包括职员的新增,检索,更新和删除。远程接口定义了下列的业务方法(为了简洁清楚,省略了方法中的throws子句)
//译者注:这里的EmployeeValueObject类型对象,译者认为是代表职员实体信息的对象。
public void createEmployee( EmployeeValueObject emplVo )
public EmployeeValueObject getEmployeeBySocialSecNum( String ssn )
public void updateEmployee( EmployeeValueObject emplVo )
public void deleteEmployee( EmployeeValueObject emplVo )
测试getEmployeeBySocialSecNum()方法
需要植入一条数据到目标数据库中,另外,测试deleteEmployee()方法和updateEmployee()方法时,同样也是在先前植入的这条记录的基础上进行。最后,测试类会首先利用createEmployee()方法创建一条记录,同时我们需要校验执行这个方法时,是否会有异常发生。
下面这个DBUnit种子文件,叫做"employee_hr_seedxml",下面将用到这个文件。
<xml version='10' encoding='UTF-8'>
<dataset>
<EMPLOYEE employee_uid='1'
start_date='2001-01-01'
first_name='Drew' ssn='333-29-9999'
last_name='Smith' />
<EMPLOYEE employee_uid='2'
start_date='2002-04-04'
first_name='Nick' ssn='222-90-1111'
last_name='Marquiss' />
<EMPLOYEE employee_uid='3'
start_date='2003-06-03'
first_name='Jose' ssn='111-67-2222'
last_name='Whitson' />
</dataset>
测试类 EmployeeSessionFacadeTest
,需要扩展DBUnit的基础类DatabaseTestCase并且必须提供对getConnection()和getDataSet()方法的实现,在getConnection()方法中将获得与EJB容器初始化时一样的数据库实例,getDataSet()方法负责读取上面提及的employee_hr_seedxml文件的数据。
测试方法相当简单,因为DBUnit已经为我们处理了复杂的数据库生命周期任务。为了测试getEmployeeBySocialSecNum()方法,只需要简单的传递一个存在于种子文件中的社保代码号即可,比如
"333-29-9999"
//译者注:EmployeeFacade 类型对象,译者认为是代表底层数据库数据的映射体
public void testFindBySSN() throws Exception{
EmployeeFacade facade = //obtain somehow
EmployeeValueObject vo =
facadegetEmployeeBySocialSecNum("333-29-9999");
TestCaseassertNotNull("vo shouldn't be null", vo);
TestCaseassertEquals("should be Drew",
"Drew", vogetFirstName());
TestCaseassertEquals("should be Smith",
"Smith", vogetLastName());
}
为了确保 *** 作周期中的创建职员方法createEmployee()没有问题,我们只需简单的执行一下这个方法,然后校验一下看有没有异常抛出,另外,下一步我们要做的就是在这条新增的记录上进行查找 *** 作,看是否可以找到刚创建的记录。
public void testEmployeeCreate() throws Exception{
EmployeeValueObject empVo = new EmployeeValueObject();
empVosetFirstName("Noah");
empVosetLastName("Awan");
empVosetSSN("564-55-5555");
EmployeeFacade empFacade = //obtain from somewhere
empFacadecreateEmployee(empVo);
//perform a find by ssn to ensure existence
}
测试updateEmployee()方法包括四步,首先查找我们需要被更新的那条记录,然后更新它,紧接着,重新查找这条记录,确认更新 *** 作是否有效。
public void testUpdateEmployee() throws Exception{
EmployeeFacade facade =//obtain facade
EmployeeValueObject vo =
facadegetEmployeeBySocialSecNum("111-67-2222");
TestCaseassertNotNull("vo was null", vo);
TestCaseassertEquals("first name should be Jose", "Jose",
vogetFirstName());
vosetFirstName("Ramon");
facadeupdateEmployee(vo);
EmployeeValueObject newVo =
facadegetEmployeeBySocialSecNum("111-67-2222");
TestCaseassertNotNull("vo was null", newVo);
TestCaseassertEquals("name should be Ramon", "Ramon",
newVogetFirstName());
}
确保数据 *** 作周期中的删除 *** 作deleteEmployee()的方法和testUpdateEmployee()方法基本类似。
它分为三步:首先查找一个已存在的记录实体,然后移除,最后再对相同的记录进行查找,确认这条记录没有被查到。
public void testDeleteEmployee() throws Exception{
EmployeeFacade facade = //obtain facade
EmployeeValueObject vo = facadegetEmployeeBySocialSecNum("222-90-1111");
TestCaseassertNotNull("vo was null", vo);
facadedeleteEmployee(vo);
try{
EmployeeValueObject newVo =
facadegetEmployeeBySocialSecNum("222-90-1111");
TestCasefail("returned removed employee");
}catch(Exception e){
//ignore
}
}
上述这些测试代码很简单也很容易理解因为这些代码唯一的职责就是测试,已经完全从系统程序代码中独立出来,这使测试变的简单。并且,这些测试用例的自动化也很容易实现。
与Ant的结合
相对于扩展DBUnit中的基础类DatabaseTestCase,DBUnit框架中自带Ant功能,允许我们可以在Ant的buildxml文件中控制数据库的状态这个功能是相当强大的,因为对于作成的诸多测试用例,它提供了一个相当简洁的解决方案。比如。在Ant中运行JUnit测试,就象下面定义一个任务一样简单明了。
<junit printsummary="yes" haltonfailure="yes">
<formatter type="xml"/>
<batchtest fork="yes"
todir="${reportstests}">
<fileset dir="${srctests}">
<include name="/Testjava"/>
</fileset>
</batchtest>
</junit>
DBUnit任务过程中,为了在Junit任务前后控制数据库的状态,我们需要创建一个"setup" *** 作,在这个 *** 作中种子文件中的数据内容会被插入的数据库中。
<taskdef name="dbunit"
classname="orgdbunitantDbUnitTask"/>
<dbunit driver=" orggjtmmmysqlDriver "
url=" jdbc:mysql://127001/hr "
userid="hr"
password="hr">
<operation type="INSERT"
src="seedFilexml"/>
</dbunit>
然后,还需要一个"tear
down" *** 作,在这个 *** 作中,"setup" *** 作插入的记录被从目标数据库中删除了。
<dbunit driver=" orggjtmmmysqlDriver "
url=" jdbc:mysql://127001/hr "
userid="hr"
password="hr">
<operation type="DELETE"
src="seedFilexml"/>
</dbunit>
用上面的代码来包装JUnit任务,能够在批量测试前有效的装载数据到目标数据库中,并且在测试结束后,将已装载的全部数据删除。
<taskdef name="dbunit"
classname="orgdbunitantDbUnitTask"/>
<!-- set up operation -->
<dbunit driver=" orggjtmmmysqlDriver "
url=" jdbc:mysql://127001/hr "
userid="hr"
password="hr">
<operation type="INSERT"
src="seedFilexml"/>
</dbunit>
<!-- run all tests in the source tree -->
<junit printsummary="yes" haltonfailure="yes">
<formatter type="xml"/>
<batchtest fork="yes" todir="${reportstests}">
<fileset dir="${srctests}">
<include name="/Testjava"/>
</fileset>
</batchtest>
</junit>
<!-- tear down operation -->
<dbunit driver=" orggjtmmmysqlDriver "
url=" jdbc:mysql://127001/hr "
userid="hr"
password="hr">
<operation type="DELETE"
src="seedFilexml"/>
</dbunit>
结论
能够在测试周期内管理数据库的状态,DBUnit框架的这个功能特性使得测试用例代码的创建和应用的周期大大缩短。而且,通过控制数据库这个主要的依赖对象,使的利用DBUnit框架的的测试更容易自动化。
DBUnit精妙的设计,使的学习使用它变得很简单。在你的测试方案中,如果你能够正确的使用它,那么带来的将是代码稳定性方面的大幅度增强,当然还会使你的开发团队信心倍增。
简单回答就是:不用测。原则是,除了统计类需求、业务逻辑在数据库里、数据库性能测试 这3个需求外,没有必要专门测试数据库。
很多(标准三层)项目,只写数据访问上层业务逻辑层的单元测试。因为
在某些项目,ORM框架稳定,开发规范完备的情况下,测试数据库是完全没有必要的
测试数据库很慢,很慢……
使用一些Mock工具,或者用依赖注入等方法,可以手动写一些直接返回硬编码数据的方法替换掉数据库 *** 作。相关工具如TypeMock, Rhino Mocks等。
对2a的解释:
单独测试插入、更新方法基本上没有必要。因为数据库不会出错(基本假设),出错的主要可能是
数据库结构改变了(这种情况应该用规范数据库变动制度来完成约束)
SQL语句写错了(一般使用稳定的ORM框架能避免大部分此类错误,所以不用测;第1条里描述的情况除外)
如果非要测,可以考虑使用专门的数据库测试工具,VS里就自带了一个。自己写的话挺麻烦的。
一天,我要妈妈跟我买一个金鱼,妈妈答应了,金鱼长得很漂亮,一双大大的眼睛,金**的皮肤……
那是晚上十点多,我起来喝水,经过客厅时,忽然看见那只金鱼还睁着那双大大的眼睛,却一动不动。我在那观察了一会儿,觉得有点困,眼皮几乎要打架了,于是就去睡了……第二天早晨,我又去观察,发现金鱼在鱼缸里自由自在地游来游去,我觉得很奇怪。当天我特意很晚才睡,因为我想继续观察那只金鱼。在12点左右,我又发现那只金鱼睁着眼睛却一动不动。我跑到书房,找《十万个为什么》。我找呀找,终于找到了。我跑到客厅,坐到沙发上,查找目录,“快,快……找到了!一百三十六页……这!”……
最后,我终于明白金鱼为什么睁着眼睛睡了——原来,金鱼不和人一样,金鱼是没有眼皮的!所以,金鱼就算很困了,它也不会眼皮打架……
观察中的发现
上个星期天的下午,我爸爸骑电动车带我去圣狮大桥那边的一块空草地上玩。我们在草地上摘野花玩的时候,突然,一个什么东西从我们身边闪过。我和爸爸追上前一看,原来是一只绿色的蝗虫。我爸爸伸手去抓,它又向前一跳,一会儿功夫便消失得无影无踪。它竟然能跳这么高,是什么力量使它能跳这么高呢?为了找这个问题的答案,我们决定抓一只蝗虫,仔细观察一下。我爸爸继续向前追,终于把它抓住了。原来蝗虫绿绿的身子上长着一双翅膀和六条腿。它的后腿很特别,又长又强壮,边上还有一排小刺,看起来很有力气。但它的前腿要比后腿短很多。我想:它能跳这么高,肯定跟它粗壮的后腿有关。后来,我爸爸怕它跑了,就把它的两只后腿和翅膀弄掉了,放在地上,我们继续观察。惊奇地发现它不跳了,怎么动它也跳不起来了。这时,我更加坚信,自己的想法是对的。为了证实这个想法。我爸爸在电脑上查阅了一些资料,资料证实:它能跳那么高是它的后腿的作用。
通过观察,我还发现蝗虫腹部两侧有一行小洞,我想是蝗虫用来呼吸的,这个问题希望大家能和我一起来探讨。
我的观察发现
听爷爷说,一般的鸡蛋在水里是不会浮起来的。我想亲自做个实验,观察鸡蛋在怎样的条件下才能浮起来。
我先把生鸡蛋分别放在装有冷水和热水的容器里,但是鸡蛋沉到水底了;那么用熟鸡蛋来实验,是否会浮起来呢?我又把熟鸡蛋分别放在冷水和热水里,鸡蛋还是沉到水底了,试验两次都失败了,我心里很难受。就跑去请教爸爸,爸爸说:“只有改变水的密度,鸡蛋才能浮起来!”
“把水里加入适量的盐,鸡蛋就可以浮起来了!”
我按照爸爸教我的方法,先放了20克的盐,鸡蛋还是没有浮起来,我又加了20克,也没有浮起来,再加10克,鸡蛋就缓缓的浮了起来!
我高兴的告诉爸爸:“我成功了!”
这次试验,我学到了一个道理:只有在水里加适量的盐,鸡蛋才能浮起来。
以上就是关于有没有技术大牛了解java开发,测试和CI的关全部的内容,包括:有没有技术大牛了解java开发,测试和CI的关、DbUnit (安装 | 使用)、C# 单元测试等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)