单元测试(JUnit)的应用

单元测试(JUnit)的应用

一、  概要

单元测试不仅仅是保证代码在方法级别的正确性,它还能改进设计,易于对代码重构。凡是容易编写单元测试的代码,往往是优秀的设计和松耦合的组件,凡是难于编写单元测试的代码,往往是设计不佳和耦合度高的系统,因此,编写单元测试不仅仅是掌握单元测试框架的用法,更重要的是在编写单元测试的过程中发现设计缺陷,改进系统结构,从而实现良好的可扩展性。

任何一个项目,单元测试应该在详细设计之后开始进行,首先根据详细设计文档进行单元测试用例的编写,编写完成后进行代码开发,代码完成后运行单元测试,如果通过,则该方法可以发布运行,如果不通过需要进行代码改造,再进行单元测试,直到单元测试运行通过为止。

每新增一个功能时的开发流程如图1所示:

单元测试(JUnit)的应用

图1

本文首先按照上图所示流程进行一个简单功能的开发,同时使用JUnit 4 提供的各种功能开展有效的单元测试,接着对JUnit技术进行了简单的介绍。

二、    软件支持

Ø  Eclipse:最为流行的IDE,它全面集成了JUnit,并从版本3.2开始支持 JUnit 4当然JUnit 并不依赖于任何 IDE。您可以从 http://www.eclipse.org/上下载最新的Eclipse 版本。

Ø  JUnit:它的官方网站是 http://www.junit.org/。您可以从上面获取关于 JUnit 的最新消息。如果您和本文一样在 Eclipse 中使用 JUnit,就不必再下载了

Ø  Ant:基于 Java 的开源构建工具,您可以在http://ant.apache.org/ 上得到最新的版本和丰富的文档。Eclipse中已经集成了 Ant。

三、  JUnit4的实际应用例子

3.1 应用例子的程序功能需求

            将Java对象名称(每个单词的头字母大写)按照数据库命名的习惯进行格式化,格式化后的数据为小写字母,并且使用下划线分割命名单词,要求对输入数据进行非法验证。

3.2 创建Java工程

首先新建一个Java工程——TestJUnit。打开项目TestJUnit的属性页->选择“JavaBuild Path”子选项->点选“AddLibrary…”按钮 -> 在弹出的“AddLibrary”对话框中选择 JUnit图2),并在下一页中选择版本4.1后点击“Finish”按钮。

单元测试(JUnit)的应用

图2

请注意 JDK 的版本:

JUnit 4.1 是基于 Java5 的升级版本,它使用了 Tiger 中的很多新特性来简化原有的使用方式。正因为如此,它并不能直接运行在 JDK1.4.x 版本上。如果您需要在 JDK1.4.x 版本使用 JUnit 的话,请使用 3.8.1 版本

我们在项目 TestJUnit 根目录下添加一个新目录 testsrc,并把它加入到项目源代码目录中(加入方式见 图3)以存放测试代码,使之与源代码分离。

单元测试(JUnit)的应用

图3

3.3 编写被测试程序代码

按照程序功能需求所写的代码如下:

import java.util.regex.Matcher;

import java.util.regex.Pattern;

 

/**

 *对名称、地址等字符串格式的内容进行格式检查

 *@authorcws

 */

publicclass WordDealUtil{

 

    /**

     *Java对象名称(每个单词的头字母大写)按照

     *数据库命名的习惯进行格式化

     *格式化后的数据为小写字母,并且使用下划线分割命名单词

     *

     *例如:employeeInfo经过格式化之后变为employee_info

     *

     *@paramnameJava对象名称

     */

    publicstatic String wordFormat4DB(String name){

       Pattern p = Pattern.compile("[A-Z]"); //将给定的正则表达式编译并赋予给Pattern

       Matcher m = p.matcher(name); //生成一个给定命名的Matcher对象

       StringBuffer sb = new StringBuffer();

      

       while(m.find()){

           m.appendReplacement(sb, "_"+m.group());//Matcher.group()返回匹配到的子字符串

       }

       return m.appendTail(sb).toString().toLowerCase();

    }

}

 

3.3 编写测试代码

一、可以直接添加普通类,编写的单元测试用例:

二、也可以创建Junit测试类

1. 右击test测试包,选择New-->Other...

单元测试(JUnit)的应用

2. 在窗口中找到Junit,选择Junit TestCase

单元测试(JUnit)的应用

3. 输入名称(Name),命名规则一般建议采用:类名+Test或者Test+类名

Browse...选择要测试的类,这里是WordDealUtil,点击“Next>”。

单元测试(JUnit)的应用

4. 勾选要测试的方法,点击“Finish”。

     单元测试(JUnit)的应用

5. 生成后效果如下

单元测试(JUnit)的应用

这里import static是引入Assert类中静态属性或静态方法的写法。原来要Assert.fail(),现在只需直接fail()即可,即省略了Assert

其实不通过Junit新建向导来建立也可以,随便建立一个新类后,只需在方法上加入@Test注解即可

 

6.再根据需要更改测试函数内部的代码

importstatic org.junit.Assert.*;

import org.junit.*;

 

publicclass TestWordDealUtil {

 

    @BeforeClass

    publicstaticvoid setUpBeforeClass() throws Exception {

       System.out.println("in BeforeClass================");

    }

 

    @AfterClass

    publicstaticvoid tearDownAfterClass() throws Exception {

       System.out.println("in AfterClass=================");

    }

 

    @Before

    publicvoid before() {

       System.out.println("in Before");

    }

 

    @After

    publicvoid after() {

       System.out.println("in After");

    }

    //测试wordFormat4DB 一般的处理情况

    @Test

    publicvoid wordFormat4DBNormal(){

       String target = "employeeInfo";

       String result = WordDealUtil.wordFormat4DB(target);

       assertEquals("employee_info", result);

    }

   

    //测试null时的处理情况

    @Test

    publicvoid wordFormat4DBNull(){

       String target = null;

       String result = WordDealUtil.wordFormat4DB(target);

       assertNull(result);

    }

   

    //测试空字符串时的处理情况

    @Test

    publicvoid wordFormat4DBEmpty(){

       String target = "";

       String result = WordDealUtil.wordFormat4DB(target);

       assertEquals("", result);

    }

   

    //测试当首字母大写时的情况

    @Test

    publicvoid wordFormat4DBegin(){

       String target = "EmployeeInfo";

       String result = WordDealUtil.wordFormat4DB(target);

       assertEquals("employee_info", result);

    }

   

    //测试当尾字母为大写时的情况

    @Test

    publicvoid wordFormat4DBEnd(){

       String target = "employeeInfoA";

       String result = WordDealUtil.wordFormat4DB(target);

       assertEquals("employee_info_a", result);

    }

   

    //测试多个相连字母大写时的情况

    @Test

    publicvoid wordFormat4DBTogether(){

       String target = "employeeAInfo";

       String result = WordDealUtil.wordFormat4DB(target);

       assertEquals("employee_a_info", result);

    }

}

运行测试,结果如图4所示:单元测试(JUnit)的应用

4

3.4 根据测试结果更改程序

JUnit 运行界面提示我们有两个测试情况未通过——当首字母大写时得到的处理结果与预期的有偏差,造成测试失败(failure);而当测试对 null 的处理结果时,则直接抛出了异常——测试错误(error)。

JUnit 将测试失败的情况分为两种:failure error

Failure一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;

Error则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),也可能是被测试代码中的一个隐藏的bug    

显然,被测试代码中并没有对首字母大写和 null 这两种特殊情况进行处理,现在对源代码进行修改,添加对这两种情况的处理,修改后的代码如下:

package com.meritit;

 

import java.util.regex.Matcher;

import java.util.regex.Pattern;

 

/**

 *对名称、地址等字符串格式的内容进行格式检查

 *@authorlvxg

 */

publicclass WordDealUtil {

 

    /**

     *Java对象名称(每个单词的头字母大写)按照

     *数据库命名的习惯进行格式化

     *格式化后的数据为小写字母,并且使用下划线分割命名单词

     *

     *例如:employeeInfo经过格式化之后变为employee_info

     *

     *@paramnameJava对象名称

     */

    publicstatic String wordFormat4DB(String name){

       if(name == null){

           returnnull;

       }

       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所示:单元测试(JUnit)的应用

5

至此,现在的代码已经比较稳定,可以作为API的一部分提供给其它模块使用了,如果测试还没有通过,则继续进行代码改进,一直等到测试完全通过时编码才完成。

您的单元测试代码不是用来证明您是对的,而是为了证明您没有错。因此单元测试的范围要全面,比如对边界值、正常值、错误值的测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。

四、  JUnit的知识点

4.1 注释/元数据

引入库类:import org.junit.*;

1. 说明

@Before

初始化方法

@After

释放资源

@Test

测试方法,在这里可以测试期望异常和超时时间

@Ignore

忽略的测试方法;

当测试的方法还没有实现,或者测试的方法已经过时,或者在某种条件下才能测试该方法(比如需要一个数据库联接,而在本地测试的时候,数据库并没有连接),那么使用该标签来标示这个方法。

@BeforeClass

针对所有测试,只执行一次,且必须为static void

@AfterClass

针对所有测试,只执行一次,且必须为static void

@RunWith

指定测试类使用某个运行器

@Parameters

指定测试类的测试数据集合

@Rule

允许灵活添加或重新定义测试类中的每个测试方法的行为

@FixMethodOrder

指定测试方法的执行顺序

 

2. 执行顺序

一个测试类单元测试的执行顺序为:

@BeforeClass –> @Before–> @Test –> @After –> @AfterClass

每一个测试方法的调用顺序为:

@Before –> @Test –>@After

 

4.2 断言静态类Assert

引入命名空间:import staticorg.junit.Assert.*;

 

assertArrayEquals(expecteds, actuals)

查看两个数组是否相等。

assertEquals(expected, actual)

查看两个对象是否相等。类似于字符串比较使用的equals()方法

assertNotEquals(first, second)

查看两个对象是否不相等。

assertNull(object)

查看对象是否为空。

assertNotNull(object)

查看对象是否不为空。

assertSame(expected, actual)

查看两个对象的引用是否相等。类似于使用“==”比较两个对象

assertNotSame(unexpected, actual)

查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象

assertTrue(condition)

查看运行结果是否为true。

assertFalse(condition)

查看运行结果是否为false。

assertThat(actual, matcher)

查看实际值是否满足指定的条件

fail()

让测试失败

 

 

4.3 多个测试类同时执行

利用org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...)函数,把test case 的类放进去,然后放在main()方法里执行。

比如同时执行测试类TestWordDealUtil、Test2:

 

publicclass TestMain {

    publicstaticvoid main(String[] args)

    {

    org.junit.runner.JUnitCore.runClasses(TestWordDealUtil.class,Test2.class);

    }

 

}