单元测试(JUnit)的应用
一.概要
单元测试不仅仅是保证代码在方法级别的正确性,它还能改进设计,易于对代码重构。凡是容易编写单元测试的代码,往往是优秀的设计和松耦合的组件,凡是难于编写单元测试的代码,往往是设计不佳和耦合度高的系统,因此,编写单元测试不仅仅是掌握单元测试柜架的用法,更重要的是在编写单元测试的过程中发现设计缺陷,改进系统结构,从而实现良好的可扩展性。
任何一个项目,单元测试应该在详细设计之后开始进行,首先根据详细设计文档进行单元测试用例的编写,编写完成后进行代码开发,代码完成后运行单元测试,如果通过,则该方法可以发布运行,如果不通过需要进行代码改造,再进行单元测试,直到单元测试运行通过为止。
每新增一个功能时的开发流程如图1所示:
图1
本文首先按照上图所示流程进行一个简单功能的开发,同时使用JUnit 4 提供的各种功能开展有效的单元测试,接着对JUnit技术进行了简单的介绍。
二.软件支持
Eclipse:最为流行的 IDE,它全面集成了 JUnit,并从版本 3.2 开始支持 JUnit 4。当然 JUnit 并不依赖于任何 IDE。您可以从 / 上下载最新的 Eclipse 版本。
Ant:基于 Java 的开源构建工具,您可以在 / 上得到最新的版本和丰富的文档。Eclipse 中已经集成了 Ant。
JUnit:它的是 /。您可以从上面获取关于 JUnit 的最新消息。如果您和本文一样在 Eclipse 中使用 JUnit,就不必再下载了。
三.JUnit的实际应用例子
3.1功能需求
将Java对象名称(每个单词的头字母大写)按照数据库命名的习惯进行格式化,格式化后的数据为小写字母,并且使用下划线分割命名单词,要求对输入数据进行非法验证。
3.2编写测试
首先新建一个 Java 工程 —— TestJUnit。打开项目 TestJUnit的属性页 -> 选择“Java Build Path”子选项 -> 点选“Add Library…”按钮 -> 在弹出的“Add Library”对话框中选择 JUnit(图2),并在下一页中选择版本 4.1 后点击“Finish”按钮。
图2
请注意 JDK 的版本:
JUnit 4.1 是基于 Java 5 的升级版本,它使用了 Tiger 中的很多新特性来简化原有的使用方式。正因为如此,它并不能直接运行在 JDK1.4.x 版本上。如果您需要在 JDK1.4.x 版本使用 JUnit 的话,请使用 3.8.1 版本。
我们在项目 TestJUnit 根目录下添加一个新目录 testsrc,并把它加入到项目源代码目录中(加入方式见 图3)以存放测试代码,使之与源代码分离。
图3
一切准备就绪,下面是编写的单元测试用例:
package itit; import static org.junit.Assert.AssertNull; 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); } } |
3.3编写代码
按照需求所写的代码如下:
package itit; import Matcher; import Pattern; /** * 对名称、地址等字符串格式的内容进行格式检查 * @author lvxg */ public class WordDealUtil { /** * 将Java对象名称(每个单词的头字母大写)按照 * 数据库命名的习惯进行格式化 * 格式化后的数据为小写字母,并且使用下划线分割命名单词 * * 例如:employeeInfo 经过格式化之后变为 employee_info * * @param name Java对象名称 */ 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(); } } |
运行测试,结果如图4所示:
图4
JUnit 运行界面提示我们有两个测试情况未通过——当首字母大写时得到的处理结果与预期
的有偏差,造成测试失败(failure);而当测试对 null 的处理结果时,则直接抛出了异常——测试错误(error)。
JUnit 将测试失败的情况分为两种:failure 和 error。Failure 一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;而 error 则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),也可能是被测试代码中的一个隐藏的bug。 显然,被测试代码中并没有对首字母大写和 null 这两种特殊情况进行处理,现在对源代码进行修改,添加对这两种情况的处理,修改后的代码如下:
package itit; import Matcher; import Pattern; /** * 对名称、地址等字符串格式的内容进行格式检查 * @author lvxg */ public class WordDealUtil { /** * 将Java对象名称(每个单词的头字母大写)按照 * 数据库命名的习惯进行格式化 * 格式化后的数据为小写字母,并且使用下划线分割命名单词 * * 例如:employeeInfo 经过格式化之后变为 employee_info * * @param name Java对象名称 */ public static String wordFormat4DB(String name){ if(name == null){indexof能用于数组吗 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(); } } |
再次运行测试,显示的测试结果如图5所示:
图5
至此,现在的代码已经比较稳定,可以作为 API 的一部分提供给其它模块使用了,如果测试还没有通过,则继续进行代码改进,一直等到测试完全通过时编码才完成。
您的单元测试代码不是用来证明您是对的,而是为了证明您没有错。因此单元测试的范围要全面,比如对边界值、正常值、错误值的测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。
四.JUnit深入
当然,JUnit 提供的功能决不仅仅如此简单,在接下来的内容中,我们会看到 JUnit 中很多有用的特性,掌握它们对您灵活的编写单元测试代码非常有帮助。
4.1Fixture
何谓 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 方法注销测试环境。注意是每一个测试方法的执行都会触发对公共 Fixture 的设置,也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的(图6)。这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论