推荐学习书籍:JUnit in Action(JUnit实战),学习所有框架时都建议找一下xxx in Action这种书籍。
一、junit的安装和测试原则
1、导入junit的jar包(junit-4.12.jar),hamcrest-core-1.3.jar,特别注意:使用junit-4.12时必须导入
hamcrest-core的jar包,或直接导入hamcrest-all-1.3.jar包,不建议直接使用eclipse中所提供的jar包。
2、在src目录下编写测试代码
3、创建测试类
基本原则:1、在eclipse中创建一个source folder命名为test。
2、创建一个测试类所在的包,包的名称和要测试的类一致。
4、Junit3和Junit4两者之间的区别是非常明显的
在Junit3中,如果某个类需要是测试类,必须将其继承于TestCase,如果某个方法需要是测试方法,必须让这个方法通过testXX开头,在junit3中,如果希望指定某个测试方法运行之前运行某个初始化方法,这个方法的名称必须是setUp,如果希望在某个测试方法运行之后运行某个释放资源的方法,这个方法的名称必须是tearDown。
在Junit4中,一个POJO类就是一个测试类,测试方法通过@Test来标识,初始化方法通过@Before,释放资源的方法通过@After来标注。但是为了让junit4中的测试类在Junit3中也可以使用,习惯于把初始化方法命名为setUp,释放资源的方法命名为tearDown,测试方法也同样以test开头。
5、如何使用断言
在junit4中提供了一个Assert的类,这个类中有大量的方法进行断言的处理,在junit3中由于继承了TestCase,这个TestCase中提供了大量的assert的方法。
package org.pm.util;
//把Assert中的static方法全部导入到类中
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class TestCalculate {
Calculate cal;
//执行任意一个方法之前都会执行setUp方法
@Before
public void setUp() {
cal = new Calculate();
}
//加了@Test表示该方法是一个单元测试方法
@Test
public void testAdd() {
int rel = cal.add(12, 22);
/*
* 以下就是一个简单的断言的编写
* 第一个参数是如果出错给出的提示信息
* 第二个参数表示方法执行完成之后预期的一个值
* 第三个参数表示实际值
*/
//Assert.assertEquals("加法错误", rel, 34);
/**
* 当进行了静态导入之后,import static org.junit.Assert.*;
* Assert中的所有静态方法就不用在添加类名来调用
* 这样就可以有效的兼容junit3
*/
assertEquals("加法错误", rel, 34);
}
}
6、测试异常对象
//表示这个测试类应该抛出ArithmeticException,如果不抛出异常就报错
@Test(expected=ArithmeticException.class)
public void divideException() {
int rel = cal.divide(20, 0);
}
7、timeout参数
//表示这个方法应该在300毫秒内执行结束才算是正确
@Test(timeout=300)
public void testTime() {
//可以用来进行一些简单的性能测试
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("abc");
}
二、hamcrest简介和TestSuite
1、hamcrest(需要导入hamcrest-library-1.3.jar包,如果已导入hamcrest-all-1.3.jar包则不需导入此包)
hamcrest可以有效增加junit的测试能力,用一些相对通俗的语言来进行测试。
要使用junit中的assertThat来进行断言,
第一个参数表示实际值,第二个参数表示hamcrest的表达式。
@Test
public void testHamcrest() {
//首先需要静态导入:import static org.hamcrest.Matchers.*;
//判读50是否大于20并且小于60,具体的hamcrest的比较参数可以在文档中查询
assertThat(50, allOf(greaterThan(20),lessThan(60)));
//判读某个字符串是否以另一个字符串结尾
assertThat("abc.txt", endsWith("txt"));
}
特别注意:如果使用junit4.10,必须把hamcrest的jar包移到junit的jar之前,否则,组合条件allOf,anyOf都会抛出异常。
常用的比较方式:
逻辑:
allOf - 如果所有匹配器都匹配才匹配, short circuits (很难懂的一个词,意译是短路,感觉不对,就没有翻译)
(像 Java &&)。
anyOf - 如果任何匹配器匹配就匹配, short circuits (像 Java ||)。
not - 如果包装的匹配器不匹配器时匹配,反之亦然。
对象:
equalTo - 测试对象相等使用Object.equals方法。
hasToString - 测试Object.toString方法。
instanceOf, isCompatibleType - 测试类型。
notNullValue, nullValue - 测试null。
sameInstance - 测试对象实例。
Beans。
hasProperty - 测试JavaBeans属性。
集合:
array - 测试一个数组元素test an array’s elements against an array of matchers。
hasEntry, hasKey, hasValue - 测试一个Map包含一个实体,键或者值。
hasItem, hasItems - 测试一个集合包含一个元素。
hasItemInArray - 测试一个数组包含一个元素。
数字:
closeTo - 测试浮点值接近给定的值。
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 测试次序。
文本:
equalToIgnoringCase - 测试字符串相等忽略大小写。
equalToIgnoringWhiteSpace - 测试字符串忽略空白。
containsString, endsWith, startsWith - 测试字符串匹配。
2、TestSuite
可以通过TestSuite来组成多个测试组件。
//RunWith表示这个类是一个suite的类
@RunWith(Suite.class)
//说明这个类中包含哪些测试组件
@SuiteClasses({TestA.class,
TestB.class,
TestCalculate.class})
public class TestSuite {
/*
* 测试原则:
* 1、建议创建一个专门的source folder-->test来编写测试类代码
* 2、测试类的包应该保持和需要测试的类一致
* 3、测试单元中的每一个测试方法都必须可以独立执行,没有顺序
* 4、测试方法之间不能有任何的依赖性
*/
}
三、测试驱动开发
测试:
1、黑盒测试:黑盒测试也称功能测试,它是通过测试来检测每个功能是否都能正常使用。在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试。
2、白盒测试:白盒测试又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。白盒测试是一种测试用例设计方法,盒子指的是被测试的软件,白盒指的是盒子是可视的,你清楚盒子内部的东西以及里面是如何运作的。"白盒"法全面了解程序内部逻辑结构、对所有逻辑路径进行测试。"白盒"法是穷举路径测试。在使用这一方案时,测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。
正常的开发流程:编码--->测试--->重复--->提交
基于测试驱动的开发:测试--->编码--->重复--->提交
先写了测试之后,由于测试的覆盖率要求为100%,所以就会让代码中可能存在的分支都进行测试,这样先写测试单元,可以为将来的代码提供一种有效的参考。
cobertura的使用:
1、将cobertura的路径设置到path中;
2、将要测试的源代码,编译之后的字节码文件和所需要的jar包拷贝到一个单独的目录中做处理,习惯将
lib和src文件夹放到bin文件夹下;
3、在命令提示符中使用命令为要生成测试覆盖率报告的代码生成一个ser的文件;
3.1、生成cobertura.ser文件
cobertura-instrument --destination instrumented org/pm/service/
4、基于ser文件文件运行测试;
4.1、测试命令:(特别注意:此命令需要被测试的Java类在jdk1.6下编译的文件才能正常运行,jdk1.8下
编译的文件执行此命令会报错。),使用cobertura-2.1.1.jar也可以。
java -cp lib/junit-4.12.jar;lib/cobertura.jar;lib/hamcrest-core-1.3.jar;instrumented;.;-Dnet.sourceforge.cobertura.datafile=cobertura.serorg.junit.runner.JUnitCore org.pm.service.TestUserService
使用junit-4.12.jar报错:缺少jar包,增加hamcrest-core-1.3.jar即可。
5、根据ser文件生成测试覆盖率的报告
特别注意:如果文件的编码是utf-8的,在生成报告前需要为cobertura-report.bat文件增加DfileEncoding的处理。
增加:-Dfile.encoding=utf-8
java -cp "%COBERTURA_HOME%cobertura.jar;%COBERTURA_HOME%lib\asm-3.0.jar;%COBERTURA_HOME%lib\asm-tree-3.0.jar;%COBERTURA_HOME%lib\log4j-1.2.9.jar;%COBERTURA_HOME%lib\jakarta-oro-2.0.8.jar" -Dfile.encoding=utf-8 net.sourceforge.cobertura.reporting.Main %CMD_LINE_ARGS%
生成测试覆盖率命令:
cobertura-report --format html --datafile cobertura.ser --destination reports src
四、stub和mock(建议学习书籍:mocks arent stubs)
1、dbunit:用来隔离数据库的访问
1.1、环境搭建
1、导入jar包(dbunit.jar,slf4j.jar)
2、创建dbunit的测试数据xml文件
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<!-- <t_user>
<id>1</id>
<username>pm</username>
<password>123</password>
<nickname>项目经理</nickname>
</t_user> -->
<t_user id="1" username="admin" password="123" nickname="超级管理员"/>
</dataset>
3、创建dbunit的Connection
dbunit的Connection是用来对数据文件进行操作的,这个Connection必须依赖于目前项目中所使用的
Connection。
IDatabaseConnection con = new DatabaseConnection(DbUtil.getConnection());
4、创建IDataSet,通过DataSet来获取测试数据中的数据
/*
* FlatXmlDataSet用来获取基于属性存储的属性值
* XMLDataSet用来获取基于节点类型存储的属性值
*/
IDataSet ds = new FlatXmlDataSet(
new FlatXmlProducer(
new InputSource(
TestDbUnit.class.getClassLoader().getResourceAsStream("t_user.xml"))));
5、初始化数据并且完成测试
//会将数据库中的数据清空,并且把测试数据插入
DatabaseOperation.CLEAN_INSERT.execute(con, ds);
//从DAO中获取数据并且完成测试
IUserDao ud = new UserDao();
User tu = ud.load("admin");
assertEquals(tu.getId(), 1);
assertEquals(tu.getUsername(), "admin");
assertEquals(tu.getPassword(), "123");
assertEquals(tu.getNickname(), "超级管理员");
1.2、备份和还原数据库
1、备份:
@Test
public void testBackup() {
try {
//创建dbunit的Connnection,需要传入一个数据库的connection作为参数
IDatabaseConnection con = new DatabaseConnection(DbUtil.getConnection());
//根据con创建相应的dataset,这个dataset包含了所有的表
IDataSet ds = con.createDataSet();
//将ds中的数据通过FlatXmlDataSet的格式写到文件中
FlatXmlDataSet.write(ds, new FileWriter("d:/test.xml"));
} catch (DataSetException e) {
e.printStackTrace();
} catch (DatabaseUnitException e) {
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
以上演示的是备份数据库的所有文件。
备份某些特定的表:
public void testBackupTable() {
try {
//创建dbunit的Connnection,需要传入一个数据库的connection作为参数
IDatabaseConnection con = new DatabaseConnection(DbUtil.getConnection());
//通过QueryDataSet可以有效的选择要处理的表来作为数据集
QueryDataSet backup = new QueryDataSet(con);
//添加t_user这张表作为备份表
backup.addTable("t_user");
FlatXmlDataSet.write(backup, new FileWriter("d:/test.xml"));
2、还原数据库
2、easymock
mock对象:用来对一些未实现关联对象的类进行测试的对象。
mock和stub的区别:
1、 mock关注的是交互。
2、stub关注的是状态。
EasyMock就是实现Mock对象的框架。
2.1、运行环境:
1、导入easymock的jar包
2、Mock对象的生命周期,三个阶段:record,replay,verify。
Mock的关注点是在交互上,主要解决的问题是对象之间的交互,诸如:Service就依赖于DAO,如果
DAO没有实现,我们可以通过Mock来模拟DAO的实现。
record阶段就是用来说明这个DAO上面可能存在的值:
@Test
public void testLoad() {
//1、创建DAO的Mock对象,目前就进入了record阶段
IUserDao ud = EasyMock.createMock(IUserDao.class);
User u = new User(1,"admin","123","管理员");
//2、记录ud可能会发生的操作的结果
/*
* 以下代码所指的是,当在dao中调用了load方法并且参数为admin的时候,返回值是u对象
*/
//必须把交互的所有过程都记录下来
EasyMock.expect(ud.load("asd")).andReturn(u);
ud.delete("abc");
//以下用来操作没有返回值的方法
EasyMock.expectLastCall();
EasyMock.expect(ud.load("admin")).andReturn(u);
//3、进入测试阶段,也就是replay阶段
EasyMock.replay(ud);
//创建Service和DAO的关联
IUserService us = new UserService(ud);
//完成测试
User tu = us.load("admin");
EntitiesHelper.assertUser(tu,u);
//3、验证交互关系是否正确
EasyMock.verify(ud);
}
2.2、mock的几种创建方式
1、createMock
通过createMock所创建的mock对象,在进行verify的时候仅仅只是检查关联方法是否正常完成调用,如
果完成次数一致就认为测试通过,不考虑顺序问题。
@Test
public void testLoadMock() {
//1、创建DAO的Mock对象,目前就进入了record阶段
IUserDao ud = EasyMock.createMock(IUserDao.class);
User u = new User(1,"admin","123","管理员");
EasyMock.expect(ud.load("asd")).andReturn(u);
//使用的createMock,如果方法的调用顺序不一致,不会抛出异常
ud.delete("abc");
EasyMock.expectLastCall();
EasyMock.expect(ud.load("admin")).andReturn(u);
EasyMock.replay(ud);
//创建Service和DAO的关联
IUserService us = new UserService(ud);
//完成测试
User tu = us.load("admin");
EntitiesHelper.assertUser(tu,u);
//3、验证交互关系是否正确
EasyMock.verify(ud);
}
2、createStrictMock
在verify时不仅仅验证关联方法的调用次数还要验证顺序。
@Test
public void testLoadStrictMock() {
//1、创建DAO的Mock对象,目前就进入了record阶段
IUserDao ud = EasyMock.createStrictMock(IUserDao.class);
User u = new User(1,"admin","123","管理员");
EasyMock.expect(ud.load("asd")).andReturn(u);
//使用的createStrictMock,方法的顺序不一致,所以会抛出异常
ud.delete("abc");
EasyMock.expectLastCall();
EasyMock.expect(ud.load("admin")).andReturn(u);
EasyMock.replay(ud);
//创建Service和DAO的关联
IUserService us = new UserService(ud);
//完成测试
User tu = us.load("admin");
EntitiesHelper.assertUser(tu,u);
//3、验证交互关系是否正确
EasyMock.verify(ud);
}
3、基于MockControl的创建
/**
* 使用mockControl可以检查一组调用对象之间的关系
* 所以如果希望使用Strict的方式,而且依赖了两个类以上,这两个依赖类应该通过control的方式创建
*/
@Test
public void test05() {
//可以通过control来创建一组mock
IMocksControl mc = createStrictControl();
s1 = mc.createMock(IService1.class);
s2 = mc.createMock(IService2.class);
s1.method1();
expectLastCall();
s1.method2();
expectLastCall();
s2.method3();
expectLastCall();
s2.method4();
expectLastCall();
//让mock控制器中的进行操作
mc.replay();
ms.setS1(s1);
ms.setS2(s2);
ms.m2();
//验证mock控制器中的所有mock调用
mc.verify();
}
五、基于容器的测试(jetty)
cactus可以完成模拟J2EE的容器做测试,
可以测试Servlet,JSP,Filter和EJB,
cactus主要是基于junit3.8来进行操作的,并不支持junit4中的annotation。
1、环境搭建:
1.1、导入jar(以下有背景的jar包都要导入)
2、创建Servlet的测试类
/**
* 只要写了一个类继承于 ServletTestCase,就会拥有相应的request等方法
* 需要注意的一点就是:此时junit4中的Annotation全部不起作用
* 就得按照junit3的方法来:setUp和tearDown用来做初始化和结束释放资源
* testXX用来做测试
* @author pm
*
*/
public class TestLoginServletByCactus extends ServletTestCase {
private LoginServlet servlet;
//begin是在客户端执行的
public void beginNoSession(WebRequest request) {
request.setAutomaticSession(false);
request.addParameter("username", "pm");
}
//在服务器端执行
public void setUp() {
servlet = new LoginServlet();
}
public void testNoSession() {
//服务器端执行
Assert.assertFalse(servlet.isLogin(request));
Assert.assertEquals(request.getParameter("username"),"pm");
}
public void testSessionNoUser() {
Assert.assertFalse(servlet.isLogin(request));
}
public void testSessionHasUser() {
session.setAttribute("loginUser", new User());
Assert.assertTrue(servlet.isLogin(request));
}
public void testDoGet() throws ServletException, IOException {
servlet.doGet(request, response);
}
//客户端执行
public void endDoGet(WebResponse resp) {
try {
Assert.assertEquals(resp.getTables()[0].getCellAsText(0,0),"111");
Assert.assertEquals(resp.getTables()[0].getCellAsText(0,1),"222");
} catch (SAXException e) {
e.printStackTrace();
}
}
}
3、创建动态web环境并且配置web.xml
<servlet>
<servlet-name>ServletRedirector</servlet-name>
<servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletRedirector</servlet-name>
<url-pattern>/ServletRedirector</url-pattern>
</servlet-mapping>