目录:
Junit4 使用
Ant结合Junit4实现自动化测试
一、Junit4 使用介绍
JUnit 是 Java 社区中知名度最高的单元测试工具。它诞生于 1997 年,由 Erich Gamma 和 Kent Beck 共同开发完成。其中 Erich Gamma 是经典著作《设计模式:可复用面向对象软件的基础》一书的作者之一,并在 Eclipse 中有很大的贡献;Kent Beck 则是一位极限编程(XP)方面的专家和先驱。
麻雀虽小,五脏俱全。JUnit 设计的非常小巧,但是功能却非常强大。Martin Fowler 如此评价 JUnit:在软件开发领域,从来就没有如此少的代码起到了如此重要的作用。它大大简化了开发人员执行单元测试的难度,特别是 JUnit 4 使用 Java 5 中的注解(annotation)使测试变得更加简单。
在单元测试前首先规划单元测试代码应放在什么地方。把它和被测试代码混在一起,这显然会照成混乱,因为单元测试代码是不会出现在最终产品中的。建议分别为单元测试代码与被测试代码创建单独的目录,并保证测试代码和被测试代码使用相同的包名。这样既保证了代码的分离,同时还保证了查找的方便。
下面的例子来自开发实践:工具类 WordDealUtil 中的静态方法 wordFormat4DB 是专用于处理 Java 对象名称向数据库表名转换的方法(可以在代码注释中可以得到更多详细的内容)。下面是第一次编码完成后大致情形:
package org.wh.util; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; import org.junit.Test; public class WordDealUtil { public static String wordFormat4DB(String name){ Pattern p = Pattern.compile("[A-Z]"); Matcher m = p.matcher(name); StringBuffer sb = new StringBuffer(); while(m.find()){ m.appendReplacement(sb, "_"+m.group()); } return m.appendTail(sb).toString().toLowerCase(); } }
|
它是否能按照预期的效果执行呢?尝试为它编写 JUnit 单元测试代码如下:
package org.wh.util;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; import org.junit.Test;
public class TestWordDealUtil { // 测试 wordFormat4DB 正常运行的情况 @Test public void wordFormat4DBNormal(){ String target = "employeeInfo"; String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info", result); } } |
测试类 TestWordDealUtil 之所以使用“Test”开头,完全是为了更好的区分测试类与被测试类。测试方法 wordFormat4DBNormal 调用执行被测试方法 WordDealUtil.wordFormat4DB,以判断运行结果是否达到设计预期的效果。需要注意的是,测试方法 wordFormat4DBNormal 需要按照一定的规范书写:
1. 测试方法必须使用注解 org.junit.Test 修饰。
2. 测试方法必须使用public void修饰,而且不能带有任何参数。
测试方法中要处理的字符串为“employeeInfo”,按照设计目的,处理后的结果应该为“employee_info”。assertEquals 是由 JUnit 提供的一系列判断测试结果是否正确的静态断言方法(位于类 org.junit.Assert 中)之一,使用它将执行结果 result 和预期值“employee_info”进行比较,来判断测试是否成功。
下面简单介绍一下静态类org.junit.Assert。
该类主要包含以下22个方法:
1.assertEquals(),8个重载,用来查看对象中存的值是否是期待的值,与字符串比较中使用的equals()方法类似;
2.assertFalse()和assertTrue(),各2个重载,用来查看变量是是否为false或true,如果assertFalse()查看的变量的值是false则测试成功,如果是true则失败,assertTrue()与之相反;
3.assertSame()和assertNotSame(),各2个重载,用来比较两个对象的引用是否相等和不相等,类似于通过“==”和“!=”比较两个对象;
4.assertNull()和assertNotNull(),各2个重载,用来查看对象是否为空和不为空;
5.fail (),2个重载,意为失败,用来抛出AssertionError错误。有两个用途:首先是在测试驱动开发中,由于测试用例都是在被测试的类之前编写,而写成时又不清楚其正确与否,此时就可以使用fail方法抛出错误进行模拟;其次是抛出意外的错误,比如要测试的内容是从数据库中读取的数据是否正确,而导致错误的原因却是数据库连接失败。
单元测试的范围要全面,比如对边界值、正常值、错误值得测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。显然,以上测试才刚刚开始,需继续补充一些对特殊情况的测试:
package org.wh.util;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; import org.junit.Test;
public class TestWordDealUtil { // 测试 wordFormat4DB 正常运行的情况 @Test public void wordFormat4DBNormal(){ String target = "employeeInfo"; String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info", result); }
// 测试 null 时的处理情况 @Test public void wordFormat4DBNull(){ String target = null; String result = WordDealUtil.wordFormat4DB(target);
assertNull(result); }
// 测试空字符串的处理情况 @Test public void wordFormat4DBEmpty(){ String target = ""; String result = WordDealUtil.wordFormat4DB(target);
assertEquals("", result); }
// 测试当首字母大写时的情况 @Test public void wordFormat4DBegin(){ String target = "EmployeeInfo"; String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info", result); }
// 测试当尾字母为大写时的情况 @Test public void wordFormat4DBEnd(){ String target = "employeeInfoA"; String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info_a", result); }
// 测试多个相连字母大写时的情况 @Test public void wordFormat4DBTogether(){ String target = "employeeAInfo"; String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_a_info", result); } } |
再次运行测试。此时,JUnit提示有两个测试情况未通过测试——当首字母大写时得到的处理结果与预期的有偏差,造成测试失败(failure);而当测试对 null 的处理结果时,则直接抛出了异常——测试错误(error)。显然,被测试代码中并没有对首字母大写和 null 这两种特殊情况进行处理,修改如下:
//修改后的方法wordFormat4DB
public static String wordFormat4DB(String name){
if(name == null){
return null;
}
Pattern p = Pattern.compile("[A-Z]");
Matcher m = p.matcher(name);
StringBuffer sb = new StringBuffer();
while(m.find()){
if(m.start() != 0)
m.appendReplacement(sb, ("_"+m.group()).toLowerCase());
}
return m.appendTail(sb).toString().toLowerCase();
}
JUnit 将测试失败的情况分为两种:failure 和 error。Failure 一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;而 error 则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),也可能是被测试代码中的一个隐藏的bug。
再次运行测试。通过对 WordDealUtil.wordFormat4DB 比较全面的单元测试,现在的代码已经比较稳定,可以作为 API 的一部分提供给其它模块使用了。
当然,JUnit 提供的功能决不仅仅如此简单,在接下来的内容中,会看到 JUnit 中很多有用的特性,掌握它们对灵活的编写单元测试代码非常有帮助。
Fixture
何谓 Fixture?它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。JUnit 专门提供了设置公共 Fixture 的方法,同一测试类中的所有测试方法都可以共用它来初始化 Fixture 和注销 Fixture。和编写 JUnit 测试方法一样,公共 Fixture 的设置也很简单,只需要:
1. 使用注解 org.junit.Before修饰用于初始化 Fixture 的方法。
2. 使用注解 org.junit.After修饰用于注销 Fixture 的方法。
3. 保证这两种方法都使用public void修饰,而且不能带有任何参数。
遵循上面的三条原则,编写出的代码大体是这个样子:
//初始化Fixture方法
@Before
public void init(){ ……}
//注销Fixture方法
@After
public void destroy(){ ……}
这样,在每一个测试方法执行之前,JUnit 会保证 init 方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit 又会调用 destroy 方法注