(二)单元测试利器 JUnit 4

JUnit 深入
        当然,JUnit 提供的功能决不仅仅如此简单,在接下来的内容中,我们会看到 JUnit 中很多有用的特性,掌握它们对您灵活的编写单元测试代码非常有帮助。
Fixture
        何谓 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 设置方法是方法级别的(图5)。这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。

图5 方法级别 Fixture 执行示意图

                                           
        可是,这种 Fixture 设置方式还是引来了批评,因为它效率低下,特别是在设置 Fixture 非常耗时的情况下(例如设置数据库链接)。而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个测试方法重新设置一次 Fixture。因此在 JUnit 4 中引入了类级别的 Fixture 设置方法,编写规范如下:
1. 使用注解 org,junit.BeforeClass 修饰用于初始化 Fixture 的方法。
2. 使用注解 org.junit.AfterClass 修饰用于注销 Fixture 的方法。
3. 保证这两种方法都使用 public static void 修饰,而且不能带有任何参数。
        类级别的 Fixture 仅会在测试类中所有测试方法执行之前执行初始化,并在全部测试方法测试完毕之后执行注销方法(图6)。代码范本如下:
//类级别Fixture初始化方法
@BeforeClass public static void dbInit(){……}
 
//类级别Fixture注销方法
 @AfterClass public static void dbClose(){……}


图6 类级别 Fixture 执行示意图

                
异常以及时间测试
        注解 org.junit.Test 中有两个非常有用的参数:expected 和 timeout。参数 expected 代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,则 JUnit 会认为这个测试没有通过。这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。举例来说,方法 supportDBChecker 用于检查用户使用的数据库版本是否在系统的支持的范围之内,如果用户使用了不被支持的数据库版本,则会抛出运行时异常 UnsupportedDBVersionException。测试方法 supportDBChecker 在数据库版本不支持时是否会抛出指定异常的单元测试方法大体如下:
@Test(expected=UnsupportedDBVersionException.class)
 public void unsupportedDBCheck(){
  ……
}

        注解 org.junit.Test 的另一个参数 timeout,指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,则JUnit认为测试失败。这个参数对于性能测试有一定的帮助。例如,如果解析一份自定义的 XML 文档花费了多于 1 秒的时间,就需要重新考虑 XML 结构的设计,那单元测试方法可以这样来写:
@Test(timeout=1000)
 public void selfXMLReader(){
  ……
}

忽略测试方法
        JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行。例如下面的代码便表示由于没有了数据库链接,提示 JUnit 忽略测试方法 unsupportedDBCheck:
@ Ignore(“db is down”)
@Test(expected=UnsupportedDBVersionException.class)
 public void unsupportedDBCheck(){
  ……
}

        但是一定要小心。注解 org.junit.Ignore 只能用于暂时的忽略测试,如果需要永远忽略这些测试,一定要确认被测试代码不再需要这些测试方法,以免忽略必要的测试点。
测试运行器
        又一个新概念出现了——测试运行器,JUnit 中所有的测试方法都是由它负责执行的。JUnit 为单元测试提供了默认的测试运行器,但 JUnit 并没有限制您必须使用默认的运行器。相反,您不仅可以定制自己的运行器(所有的运行器都继承自 org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。指定方法也很简单,使用注解 org.junit.runner.RunWith 在测试类上显式的声明要使用的运行器即可:
@RunWith(CustomTestRunner.class)
public class TestWordDealUtil {
……
}

        显而易见,如果测试类没有显式的声明使用哪一个测试运行器,JUnit 会启动默认的测试运行器执行测试类(比如上面提及的单元测试代码)。一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用 JUnit 提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制 JUnit 测试方式时,显式的声明测试运行器就必不可少了。
测试套件
        在实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在我们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。为了解决这个问题,JUnit 提供了一种批量运行测试类的方法,叫做测试套件。这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。测试套件的写法非常简单,您只需要遵循以下规则:
1. 创建一个空类作为测试套件的入口。
2. 使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuiteClasses 修饰这个空类。
3. 将 org.junit.runners.Suite 作为参数传入注解 RunWith,以提示 JUnit 为此类使用套件运行器执行。
4. 将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。
5. 保证这个空类使用 public 修饰,而且存在公开的不带有任何参数的构造函数。
package com.ai92.cooljunit;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
……

/**
 * 批量测试 工具包 中测试类
 * @author Ai92
 */
@RunWith(Suite.class)
@Suite.SuiteClasses({TestWordDealUtil.class})
public class RunAllUtilTestsSuite {
}

        上例代码中,我们将前文提到的测试类 TestWordDealUtil 放入了测试套件 RunAllUtilTestsSuite 中,在 Eclipse 中运行测试套件,可以看到测试类 TestWordDealUtil 被调用执行了。测试套件中不仅可以包含基本的测试类,而且可以包含其它的测试套件,这样可以很方便的分层管理不同模块的单元测试代码。但是,您一定要保证测试套件之间没有循环包含关系,否则无尽的循环就会出现在您的面前……。
参数化测试
        回顾一下我们在小节“JUnit 初体验”中举的实例。为了保证单元测试的严谨性,我们模拟了不同类型的字符串来测试方法的处理能力,为此我们编写大量的单元测试方法。可是这些测试方法都是大同小异:代码结构都是相同的,不同的仅仅是测试数据和期望值。有没有更好的方法将测试方法中相同的代码结构提取出来,提高代码的重用度,减少复制粘贴代码的烦恼?在以前的 JUnit 版本上,并没有好的解决方法,而现在您可以使用 JUnit 提供的参数化测试方式应对这个问题。
参数化测试的编写稍微有点麻烦(当然这是相对于 JUnit 中其它特性而言):
1. 为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。
2. 为测试类声明几个变量,分别用于存放期望值和测试所用数据。
3. 为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。
4. 为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
5. 编写测试方法,使用定义的变量作为参数进行测试。
我们按照这个标准,重新改造一番我们的单元测试代码:
package com.ai92.cooljunit;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class TestWordDealUtilWithParam {

  private String expected;
 
  private String target;
 
  @Parameters
  public static Collection words(){
       return Arrays.asList(new Object[][]{
            {"employee_info", "employeeInfo"},  //测试一般的处理情况
            {null, null},       //测试 null 时的处理情况
            {"", ""},        //测试空字符串时的处理情况
            {"employee_info", "EmployeeInfo"},  //测试当首字母大写时的情况
            {"employee_info_a", "employeeInfoA"}, //测试当尾字母为大写时的情况
            {"employee_a_info", "employeeAInfo"} //测试多个相连字母大写时的情况
       });
  }
 
   /**
   * 参数化测试必须的构造函数
   * @param expected 期望的测试结果,对应参数集中的第一个参数
   * @param target 测试数据,对应参数集中的第二个参数
   */
  public TestWordDealUtilWithParam(String expected , String target){
   this.expected = expected;
   this.target = target;
  }
 
   /**
   * 测试将 Java 对象名称到数据库名称的转换
   */
  @Test public void wordFormat4DB(){
   assertEquals(expected, WordDealUtil.wordFormat4DB(target));
  }
}

        很明显,代码瘦身了。在静态方法 words 中,我们使用二维数组来构建测试所需要的参数列表,其中每个数组中的元素的放置顺序并没有什么要求,只要和构造函数中的顺序保持一致就可以了。现在如果再增加一种测试情况,只需要在静态方法 words 中添加相应的数组即可,不再需要复制粘贴出一个新的方法出来了。
JUnit 和 Ant
        随着项目的进展,项目的规模在不断的膨胀,为了保证项目的质量,有计划的执行全面的单元测试是非常有必要的。但单靠JUnit提供的测试套件很难胜任这项工作,因为项目中单元测试类的个数在不停的增加,测试套件却无法动态的识别新加入的单元测试类,需要手动修改测试套件,这是一个很容易遗忘得步骤,稍有疏忽就会影响全面单元测试的覆盖率。
        当然解决的方法有多种多样,其中将 JUnit 与构建利器 Ant 结合使用可以很简单的解决这个问题。Ant —— 备受赞誉的 Java 构建工具。它凭借出色的易用性、平台无关性以及对项目自动测试和自动部署的支持,成为众多项目构建过程中不可或缺的独立工具,并已经成为事实上的标准。Ant 内置了对 JUnit 的支持,它提供了两个 Task:junit 和 junitreport,分别用于执行 JUnit 单元测试和生成测试结果报告。使用这两个 Task 编写构建脚本,可以很简单的完成每次全面单元测试的任务。
不过,在使用 Ant 运行 JUnit 之前,您需要稍作一些配置。打开 Eclipse 首选项界面,选择 Ant -> Runtime 首选项(见图7),将 JUnit 4.1 的 JAR 文件添加到 Classpath Tab 页中的 Global Entries 设置项里。记得检查一下 Ant Home Entries 设置项中的 Ant 版本是否在 1.7.0 之上,如果不是请替换为最新版本的 Ant JAR 文件。

图7 Ant Runtime 首选项

                    

        剩下的工作就是要编写 Ant 构建脚本 build.xml。虽然这个过程稍嫌繁琐,但这是一件一劳永逸的事情。现在我们就把前面编写的测试用例都放置到 Ant 构建脚本中执行,为项目 coolJUnit 的构建脚本添加一下内容:
<?xml version="1.0"?>
<!-- =============================================
     auto unittest task   
     ai92                                                               
     ========================================== -->
<project name="auto unittest task" default="junit and report" basedir=".">

  <property name="output folder" value="bin"/>

  <property name="src folder" value="src"/>
 
  <property name="test folder" value="testsrc"/>
 
  <property name="report folder" value="report" />

  <!-- - - - - - - - - - - - - - - - - -
          target: test report folder init                     
         - - - - - - - - - - - - - - - - - -->
  <target name="test init">
   <mkdir dir="${report folder}"/>
  </target>
 
  <!-- - - - - - - - - - - - - - - - - -
          target: compile                     
         - - - - - - - - - - - - - - - - - -->
  <target name="compile">
   <javac srcdir="${src folder}" destdir="${output folder}" />
   <echo>compilation complete!</echo>
  </target>

  <!-- - - - - - - - - - - - - - - - - -
          target: compile test cases                     
         - - - - - - - - - - - - - - - - - -->
  <target name="test compile" depends="test init">
   <javac srcdir="${test folder}" destdir="${output folder}" />
   <echo>test compilation complete!</echo>
  </target>
 
  <target name="all compile" depends="compile, test compile">
  </target>
 
  <!-- ========================================
          target: auto test all test case and output report file                     
       ===================================== -->
  <target name="junit and report" depends="all compile">
   <junit printsummary="on" fork="true" showoutput="true">
    <classpath>
     <fileset dir="lib" includes="**/*.jar"/>
     <pathelement path="${output folder}"/>
    </classpath>
    <formatter type="xml" />
    <batchtest todir="${report folder}">
     <fileset dir="${output folder}">
      <include name="**/Test*.*" />
     </fileset>
    </batchtest>
   </junit>
   <junitreport todir="${report folder}">
    <fileset dir="${report folder}">
     <include name="TEST-*.xml" />
    </fileset>
    <report format="frames" todir="${report folder}" />
   </junitreport>
  </target>
</project>

        Target junit report 是 Ant 构建脚本中的核心内容,其它 target 都是为它的执行提供前期服务。Task junit 会寻找输出目录下所有命名以“Test”开头的 class 文件,并执行它们。紧接着 Task junitreport 会将执行结果生成 HTML 格式的测试报告(图8)放置在“report folder”下。
        为整个项目的单元测试类确定一种命名风格。不仅是出于区分类别的考虑,这为 Ant 批量执行单元测试也非常有帮助,比如前面例子中的测试类都已“Test”打头,而测试套件则以“Suite”结尾等等。

图8 junitreport 生成的测试报告

                      
        现在执行一次全面的单元测试变得非常简单了,只需要运行一下 Ant 构建脚本,就可以走完所有流程,并能得到一份详尽的测试报告。您可以在 Ant 在线手册 中获得上面提及的每一个 Ant 内置 task 的使用细节。
总结
        随着越来越多的开发人员开始认同并接受极限编程(XP)的思想,单元测试的作用在软件工程中变得越来越重要。本文旨在将最新的单元测试工具 JUnit 4 介绍给您,以及如何结合 IDE Eclipse 和构建工具 Ant 创建自动化单元测试方案。并且还期望您能够通过本文“感染”一些好的单元测试意识,因为 JUnit 本身仅仅是一份工具而已,它的真正优势来自于它的思想和技术

转载于:https://www.cnblogs.com/sunshine-study/p/3652479.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/399570.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net平台的MongoDB使用

网址&#xff1a;http://www.cnblogs.com/skychen1218/p/6595759.html 前言 最近花了点时间玩了下MongoDB.Driver&#xff0c;进行封装了工具库&#xff0c;平常也会经常用到MongoDB&#xff0c;因此写一篇文章梳理知识同时把自己的成果分享给大家。 本篇会设计到Lambda表达式的…

2018程序员最佳ssh免费登陆工具

https://www.jianshu.com/p/b29b894aa60f Linux 终端 Screenshot from 2018-09-15 00-12-41.png PAC Screenshot from 2018-09-15 00-12-00.png 参考资料 讨论qq群144081101 591302926 567351477本文涉及的python测试开发库 谢谢点赞&#xff01;本文相关海量书籍下载 Wind…

Android Studio快捷键(MAC版)

用了AS一段时间了&#xff0c;感觉还是挺好用的&#xff0c;虽然还是有些小问题&#xff0c;但好处还是很明显的。。。 从Eclipse 转用AS最难受的估计就是快捷键了&#xff0c;整了好久才基本把个人在Eclipse上使用的快捷键给找差不多&#xff0c;但还是有些快捷键木有&#xf…

学习flex布局(弹性布局)

Flex是Flexible Box的缩写&#xff0c;意为弹性布局。是W3C早期提出的一个新的布局方案。可以便捷的实现页面布局&#xff0c;目前较高版本的主流浏览器都能兼容&#xff0c;兼容情况如下&#xff1a; Flex在移动端开发上已是主流&#xff0c;比如在h5页面&#xff0c;微信小程…

php创建无限级树型菜单以及三级联动菜单

http://www.php.cn/php-weizijiaocheng-373500.html 这篇文章主要介绍了php创建无限级树型菜单 &#xff0c;主要使用的是递归函数&#xff0c;感兴趣的小伙伴们可以参考一下 写递归函数&#xff0c;可考虑缓存&#xff0c;定义一些静态变量来存上一次运行的结果&#xff0c;多…

oracle数据库用脚本运行SQL语句

1. 在同一个目录下创建 runBatch.bat sqlplus sys/sangfororcl as sysdba sql.txtpausesql.txt 要执行的SQL语句 2. 双击runBatch.bat转载于:https://www.cnblogs.com/ddmiao/p/3654227.html

使用Docker镜像和仓库

为什么80%的码农都做不了架构师&#xff1f;>>> Docker镜像 由文件系统叠加而成最底端第一层是引导文件系统bootfs&#xff0c;类似grub镜像第二层是root文件系统rootfs列出镜像 huangyiHP ~ % sudo docker images REPOSITORY TAG IMAGE …

ip_forward

Linux系统缺省并没有打开IP转发功能&#xff0c;要确认IP转发功能的状态。可以查看/proc文件系统&#xff0c;使用下面命令&#xff1a; cat /proc/sys/net/ipv4/ip_forward如果上述文件中的值为0,说明禁止进行IP转发&#xff0c;如果是1,则说明IP转发功能已经打开。要想打开I…

C++primer plus第六版课后编程题答案8.3(正解)

在百度知道里面得到了正确的答案 http://zhidao.baidu.com/question/198940026560129285.html?quesup2&oldq1 #include<iostream> #include <string>//出问题时&#xff0c;使用的是cstring,但换成string一样出问题 using namespace std; struct stringy{char…

WordPress后台删除不需要的侧边栏菜单

https://www.ludou.org/remove-admin-menu-in-wordpress.html function remove_submenu() {// 删除"设置"下面的子菜单"隐私"remove_submenu_page( options-general.php, options-privacy.php );// 删除"外观"下面的子菜单"编辑"remov…

CSS 实例

CSS 实例 CSS背景 设置页面的背景颜色 设置不同元素的背景颜色 设置一个图像作为页面的背景 错误的的背景图片 如何在水平方向重复背景图像 如何定位背景图像 一个固定的背景图片&#xff08;这个图片不会随页面的其余部分滚动&#xff09; 在一个声明的所有背景属性 高级的背景…

通过ajax提交到url路由

$regBoxform.find(button).on(click, function(){/*通过ajax提交请求*/$.ajax({type:post, /*用post 方式提交*/url:/user/register, /*提交到api的指定路由路径*/dataType: json,data:{username: $regBoxform.find([name"username"]).val(),password: $regBoxform.f…

wordpress发布文章时右侧边栏选择作者的功能代码

因为本网络营销博客现在有了两个作者&#xff0c;在后台发布文章时&#xff0c;希望可以选择作者&#xff08;以前仅是一个管理员&#xff09;。通过在网上查找资料&#xff0c;并进行实践成功。特分享如下。 一 在当前使用主题目录下的functions.php中添加以下php代码&#x…

查找邮件日志

#查找收件人的邮件get-messagetrackinglog -start "10/08/2015 17:00" -end "10/08/2015 21:00" -recipients "l1xin.com" #查找发件人的邮件get-messagetrackinglog -start "10/08/2015 17:00" -end "10/12/2015 17:00" -s…

c# webbrowser  获取用户选中文字

c# webbrowser 获取用户选中文字 原文:c# webbrowser 获取用户选中文字最近一直被一个问题困扰&#xff0c;有一个文本框&#xff0c;一个webbrowser控件&#xff0c;一个上下文菜单&#xff0c; 用户用鼠标左键选中文字&#xff0c;右键点击搜索&#xff0c;就把选中的文字赋…

修改节点的属性 节点默认不允许修改

xml的节点默认是不允许修改的&#xff0c;本文也就不做处理了 XmlDocument xmlDoc new XmlDocument(); xmlDoc.Load(xmlPath); XmlElement element (XmlElement)xmlDoc.SelectSingleNode("BookStore/NewBook"); element.SetAttribute("Name", "Zhan…

敏捷现状10周年调查

敏捷现状第10届调查 已于2015年10月2号对外开放。本次调查探讨了全球范围内敏捷实施的现状。\\\ [敏捷现状]报告给出了敏捷发展趋势&#xff0c;最佳实践和成功向敏捷转型获得的经验教训等方面的洞见&#xff0c;并且这些都是软件人才的真知灼见。该报告已然成为全球范围内规模…

解决EF 4.0 中数据缓存机制

EF4.0默认开启缓存机制&#xff0c;如果想要禁用缓存机制的话&#xff0c;则须加上一句话&#xff1a;_db.CreateObjectSet().MergeOption MergeOption.OverwriteChanges; public IQueryable<T> LoadEntities(Func<T, bool> whereLambda){try{var set _db.Create…

cannot be deleted directly via the port API: has device owner network:floatingip

cannot be deleted directly via the port API: has device owner network:floatingip posted on 2015-10-13 19:26 秦瑞It行程实录 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/ruiy/p/4875605.html

170. Two Sum III - Data structure design【easy】

170. Two Sum III - Data structure design【easy】 Design and implement a TwoSum class. It should support the following operations:add and find. add - Add the number to an internal data structure.find - Find if there exists any pair of numbers which sum is e…