Java高级特性 第11节 JUnit 3.x和JUnit 4.x测试框架

Posted 小余上岸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java高级特性 第11节 JUnit 3.x和JUnit 4.x测试框架相关的知识,希望对你有一定的参考价值。

一、软件测试

  1.软件测试的概念及分类

  软件测试是使用人工或者自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。它是帮助识别开发完成(中间或最终的版本)的计算机软件(整体或部分)的正确度 、完全度和质量的软件过程。

  软件测试过程:

  

  2.软件测试的分类

  按是否关心软件内部结构和具体实现角度来分:

  • 黑盒测试(Black-box Testing)

  黑盒测试也称功能测试,测试中把被测的软件当成一个黑盒子,不关心盒子的内部结构是什么,只关心软件的输入数据与输出数据。

  •  白盒测试(White-box Testing)

  白盒测试又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。白盒指的打开盒子,去研究里面的源代码和程序结果。

  • 灰盒测试(Gray-Box Testing)

  灰盒测试,是介于白盒测试与黑盒测试之间的一种测试,灰盒测试多用于集成测试阶段,不仅关注输出、输入的正确性,同时也关注程序内部的情况。

  从软件开发过程的阶段,可分为:

  • 单元测试(Unit Testing)

  单元测试是对软件组成单元进行测试。其目的是检验软件基本组成单位的正确性。测试的对象是软件设计的最小单位:模块。Findyou又称为模块测试

    • 测试阶段:编码后

    • 测试对象:最小模块

    • 测试人员:白盒测试工程师或开发工程师

    • 测试依据:代码和注释+详细设计文档

    • 测试方法:白盒测试

    • 测试内容:模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试

  •  集成测试(Integration Testing)

  集成测试也称联合测试、组装测试,将程序模块采用适当的集成策略组装起来,对系统的接口及集成后的功能进行正确性检测的测试工作。阿旺主要目的是检查软件单位之间的接口是否正确。

    • 测试阶段:一般单元测试之后进行

    • 测试对象:模块间的接口

    • 测试人员:白盒测试工程师或开发工程师

    • 测试依据:单元测试的模块+概要设计文档

    • 测试方法:黑盒测试与白盒测试相结合

    • 测试内容:模块之间数据传输、模块之间功能冲突、模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响

  •  系统测试(System Testing)

  将软件系统看成是一个系统的测试。包括对功能、性能以及软件所运行的软硬件环境进行测试。时间大部分在系统测试执行阶段

    • 测试阶段:集成测试通过之后

    • 测试对象:整个系统(软、硬件)

    • 测试人员:黑盒测试工程师

    • 测试依据:需求规格说明文档

    • 测试方法:黑盒测试

    • 测试内容:功能、界面、可靠性、易用性、性能、兼容性、安全性等

  •  验收测试(Acceptance Testing)

  验收测试是部署软件之前的最后一个测试操作。它是技术测试的最后一个阶段,也称为交付测试。阿旺总结验收测试的目的是确保软件准备就绪,按照项目合同、任务书、双方约定的验收依据文档,向软件购买都展示该软件系统满足原始需求。

    • 测试阶段:系统测试通过之后

    • 测试对象:整个系统(包括软硬件)。

    • 测试人员:主要是最终用户或者需求方。

    • 测试依据:用户需求、验收标准

    • 测试方法:黑盒测试

    • 测试内容:同系统测试(功能...各类文档等)

二、JUnit测试框架

  1.JUnit测试框架概述

   Junit是一套框架(用于JAVA语言),由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),即用于白盒测试。

  1)使用JUnit的好处: 

  • 可以使测试代码与产品代码分开。 
  • 针对某一个类的测试代码通过较少的改动便可以应用于另一个类的测试。 
  •  易于集成到测试人员的构建过程中,JUnit和Ant的结合可以实施增量开发。 
  • JUnit是公开源代码的,可以进行二次开发。 
  • 可以方便地对JUnit进行扩展。

  2)JUnit的特征 

  • 使用断言方法判断期望值和实际值差异,返回Boolean值。 
  • 测试驱动设备使用共同的初始化变量或者实例。 
  • 测试包结构便于组织和集成运行。 
  • 支持图型交互模式和文本交互模式。

  2.JUnit测试框架包介绍

   JUnit测试框架包含了JUnit测试类所需要的所有基类,实际上这个包也是整个JUnit的框架基础。

  • Assert静态类:一系列断言方法的集合   

  Assert包含了一组静态的测试方法,用于期望值和实际值逻辑比对是否正确(这个过程称为断言),如测试失败,Assert类就会抛出一AssertionFailedError异常,JUnit测试框架将这种错误归入Failes并加以记录,同时标志为未通过测试。如果该类方法中指定一个String类型的传参则该参数将被做为AssertionFailedError异常的标识信息,告诉测试人员改异常的详细信息。 
  JUnit 提供了6大类31组断言方法,包括基础断言、数字断言、字符断言、布尔断言、对象断言。

  其中assertEquals(Object expcted,Object actual)内部逻辑判断使用equals()方法,这表明断言两个实例的内部哈希值是否相等时,最好使用该方法对相应类实例的值进行比较。 
  而assertSame(Object expected,Object actual)内部逻辑判断使用了Java运算符“==”,这表明该断言判断两个实例是否来自于同一个引用(Reference),最好使用该方法对不同类的实例的值进行比对。 
  asserEquals(String message,String expected,String actual)该方法对两个字符串进行逻辑比对,如果不匹配则显示着两个字符串有差异的地方。 
  ComparisonFailure类提供两个字符串的比对,不匹配则给出详细的差异字符。 

  • Test接口:运行测试和收集测试结果 

  Test接口使用了Composite设计模式,是单独测试用例(TestCase),聚合测试模式(TestSuite)及测试扩展(TestDecorator)的共同接口。 它的public int countTestCases()方法,用来统计测试时有多少个TestCase。另外一个方法就是public void run( TestResult ),TestResult是实例接受测试结果, run方法执行本次测试。 

  • TestCase抽象类:核心部分,定义测试中固定方法 

  TestCase是Test接口的抽象实现,(不能被实例化,只能被继承)其构造函数TestCase(string name)根据输入的测试名称name创建一个测试实例。由于每一个TestCase在创建时都要有一个名称,若测试失败了,便可识别出是哪个测试失败。 
  TestCase类中包含的setUp()、tearDown()方法。 
     setUp()方法集中初始化测试所需的所有变量和实例,并且在依次调用测试类中的每个测试方法之前再次执行setUp()方法。 
  tearDown()方法则是在每个测试方法之后,释放测试程序方法中引用的变量和实例。
  开发人员编写测试用例时,只需继承TestCase,来完成run方法即可,然后JUnit获得测试用例,执行它的run方法,把测试结果记录在TestResult之中。 

  • TestSuite测试包类:多个测试的组合 

  TestSuite类负责组装多个Test Cases。待测得类中可能包括了对被测类的多个测试,而TestSuit负责收集这些测试,使我们可以在一个测试中,完成全部的对被测类的多个测试。TestSuite类实现了Test接口,且可以包含其它的TestSuites。它可以处理加入Test时的所有抛出的异常。
  TestSuite处理测试用例有6个规约(否则会被拒绝执行测试) :
  1)测试用例必须是公有类(Public) 
  2)用例必须继承与TestCase类 
  3)测试用例的测试方法必须是公有的( Public ) 
  4) 测试用例的测试方法必须被声明为Void 
  5)测试用例中测试方法的前置名词必须是test 
  6)测试用例中测试方法误任何传递参数 

  • TestRunner运行包类

  TestRunner运行包类是运行测试代码的运行器。

  • TestResult结果类

  TestResult结果类集合了任意测试累加结果,通过TestResult实例传递给每个测试的Run()方法。TestResult在执行TestCase是如果失败会异常抛出。 

  • 其它类与接口 

  TestListener接口是个事件监听规约,可供TestRunner类使用。它通知listener的对象相关事件,方法包括测试开始startTest(Test test),测试结束endTest(Test test),错误,增加异常addError(Test test,Throwable t)和增加失败addFailure(Test test,AssertionFailedError t)。 
  TestFailure失败类是个“失败”状况的收集类,解释每次测试执行过程中出现的异常情况。其toString()方法返回“失败”状况的简要描述

  3.JUnit 3.x测试框架

  • JUnit 3.x 测试框架概述

   JUnit是一个非常强大的单元测试包,JUnit 3.x中会自动执行test开头的方法,这是依赖反射执行的

  • 使用JUnit 3.x 进行单元测试

   搭建JUnit测试框架,必须了解下面那几个方法的作用:

   1)testXxx():JUnit 3.x自动调用并执行的方法,必须是public并且不能带有参数,必须以test开头,返回值为void

   2)setUp():初始化,准备测试环境。

   3)tearDown():释放资源

   它们的调用顺序是:setUp()--->testXxx()---->tearDown()

   使用JUnit 3.x进行单元测试一般按照下列步骤进行:

   1)在Java工程中导入所需要的JUnit测试jar包,选中setUp()方法和tearDown()方法;

   2)在Java工程中选中要测试的方法并完成测试类的方法编写;

   3)执行程序,红色代表失败,绿色代表成功。

package cn.yu;
public class Calculator {
    public int add(int x, int y) { //加法
        return x + y;
    }
    public int sub(int x, int y) { //减法
        return x - y;
    }
    public int mul(int x, int y) { //乘法
        return x * y;
    }
    public int div(int x, int y) { //除法
        return x / y;
    }
    public int div2(int x, int y) { //除法  做了异常判断
        try {
            int z = x / y;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return x / y;
    }
    public void loop(int x, int y) { //死循环
        for (; ; )
            x = y;
    }
    public void unCompleted(int x, int y) { //未完成的模块:例如平方、开方等等
        //还在开发中
    }

//    public static void main(String[] args) { // 传统代码测试
//        int a = 8;
//        int b = 2;
//        Calculator calculator = new Calculator();
//        System.out.println(calculator.add(a, b));
//        System.out.println(calculator.sub(a, b));
//        System.out.println(calculator.mul(a, b));
//        System.out.println(calculator.div(a, b));
//        System.out.println(calculator.div2(a,0));
//    }
}
package cn.yu;
import junit.framework.Assert;
import junit.framework.TestCase;
public class CalculatorTest extends TestCase {
    Calculator calculator;
    public void setUp() throws Exception {
        calculator = new Calculator();
    }
    public void tearDown() throws Exception {
        super.tearDown();
    }
    /**
     *
     * Method: add(int x, int y)
     *
     */
    public void testAdd() throws Exception {
        Assert.assertEquals(calculator.add(8,2),10);
    }
    /**
     *
     * Method: sub(int x, int y)
     *
     */
    public void testSub() throws Exception {
        Assert.assertEquals(calculator.sub(8,2),7);   //预期值和实际值不符,这里在测试时会报错
    }
    /**
     *
     * Method: mul(int x, int y)
     *
     */
    public void testMul() throws Exception {
        Assert.assertEquals(calculator.mul(8,2),16);
    }
    /**
     *
     * Method: div(int x, int y)
     *
     */
    public void testDiv() throws Exception {
        Assert.assertEquals(calculator.div(8,2),4);
    }
    /**
     *
     * Method: div2(int x, int y)
     *
     */
    public void testDiv2() throws Exception {
    }
    /**
     *
     * Method: loop(int x, int y)
     *
     */
    public void testLoop() throws Exception {
    }
    /**
     *
     * Method: unCompleted(int x, int y)
     *
     */
    public void testUnCompleted() throws Exception {
    }
}

  

  4.JUnit 4.x 测试框架

  • JUnit 4.x 测试框架概述

   JUnit 4.x对JUnit框架进行了颠覆性的改变,主要利用了JDK5.0中的新特性Annotation的特点来简化测试用例的编写。

  要前面注意到,使用JUnit 3.x时有一些比较霸道的地方,表现在:

  1. 单元测试类必须继承自TestCase。

  2. 要测试的方法必须以test开头。

  采用Annotation的JUnit 4.x已经不会霸道的要求必须继承自TestCase了,而且测试方法也不必以test开头了,只要以@Test元数据来描述即可。 JUnit 4.x中常用的注解如下:  

  • @Before: 

  使用了该元数据的方法在每个测试方法执行之前都要执行一次。

  • @After: 

  使用了该元数据的方法在每个测试方法执行之后要执行一次。

  注意:@Before和@After标示的方法只能各有一个。这个相当于取代了JUnit以前版本中的setUp()(执行初始化)和tearDown()(释放资源)方法,当然还可以继续叫这个名字,也可以随意定义名字。

  • @BeforeClass:

  所有测试方法执行前执行一次,在测试类还没有实例化就已经被加载,所以用static修饰。

  • @AfterClass:

  所有测试方法执行完执行一次,在测试类还没有实例化就已经被加载,所以用static修饰。

  • @Test(expected=*.class) 

  在JUnit4.0之前,对错误的测试,我们只能通过fail来产生一个错误,并在try块里面assertTrue(true)来测试。现在,通过@Test元数据中的expected属性。expected属性的值是一个异常的类型

  • @Test(timeout=xxx): 

  该元数据传入了一个时间(毫秒)给测试方法, 如果测试方法在制定的时间之内没有运行完,则测试也失败。

  • @ignore: 

  该元数据标记的测试方法在测试中会被忽略。当测试的方法还没有实现,或者测试的方法已经过时,或者在某种条件下才能测试该方法(比如需要一个数据库联接,而在本地测试的时候,数据库并没有连接),那么使用该标签来标示这个方法。同时,你可以为该标签传递一个String的参数,来表明为什么会忽略这个测试方法。比如:@lgnore(“该方法还没有实现”),在执行的时候,仅会报告该方法没有实现,而不会执行并报错。

  • 使用JUnit 4.x 进行单元测试
package cn.yu;
public class Calculator {
    public int add(int x, int y) { //加法
        return x + y;
    }
    public int sub(int x, int y) { //减法
        return x - y;
    }
    public int mul(int x, int y) { //乘法
        return x * y;
    }
    public int div(int x, int y) { //除法
        return x / y;
    }
    public int div2(int x, int y) { //除法  做了异常判断
        try {
            int z = x / y;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return x / y;
    }
    public void loop(int x, int y) { //死循环
        for (; ; )
            x = y;
    }
    public void unCompleted(int x, int y) { //未完成的模块:例如平方、开方等等
        //还在开发中
    }

//    public static void main(String[] args) { // 传统代码测试
//        int a = 8;
//        int b = 2;
//        Calculator calculator = new Calculator();
//        System.out.println(calculator.add(a, b));
//        System.out.println(calculator.sub(a, b));
//        System.out.println(calculator.mul(a, b));
//        System.out.println(calculator.div(a, b));
//        System.out.println(calculator.div2(a,0));
//    }
}
package cn.yu;
import org.junit.*;
public class CalculatorTest4 {

    @Before
    public void setUp() throws Exception {
        System.out.println("@Before");//测试@Before
    }

    @After
    public void end() throws Exception {
        System.out.println("@After");//测试@@After
    }

    @BeforeClass
    public static void init() throws Exception {
        System.out.println("@BeforeClass");//测试@BeforeClass
    }

    @AfterClass
    public static void disstroy() throws Exception {
        System.out.println("@AfterClass");//测试@AfterClass
    }

    @Test
    public void testAdd() {
        System.out.println("@Test testAdd");//测试@Test
    }

    @Test
    public void testSub() {
        System.out.println("@Test testSub");//测试@Test
    }

    @Ignore
    public void testDiv() {
        System.out.println("@Ignore ");//测试@Ignore
    }

    @Ignore
    public void testDiv2() {
        System.out.println("@Ignore ");//测试@Ignore
    }

    @Ignore
    public void testLoop() {
        System.out.println("@Ignore ");//测试@Ignore
    }

    public void testUnCompleted() {
        System.out.println("@Ignore ");//测试未标注
    }
}

  

 三、JUnit测试套件

  如果在测试类不端增加的情况下,如何运行所有的单元测试代码类?一个个测试类的执行吗?显然繁琐且费劲。
       将要运行的测试类集成在我们的测试套件中,比如一个系统功能对应一个测试套件,一个测试套件中包含多个测试类,每次测试系统功能时,只要执行一次测试套件就可以了。

  1. 测试类及测试套件代码
  • 新建3个测试任务类:
package jtzen9.util;
import org.junit.Test;
public class TaskTest1 {
 
    @Test
    public void test() {
        System.out.println("this is TaskTest1...");
    }
 
}
/***************************************/
package jtzen9.util;
import org.junit.Test;
public class TaskTest2 {
 
    @Test
    public void test() {
        System.out.println("this is TaskTest2...");
    }
 
}
/*********************************************/ 
package jtzen9.util;
import org.junit.Test;
public class TaskTest3 {
 
    @Test
    public void test() {
        System.out.println("this is TaskTest3...");
    }
}
  当类被@RunWith注解修饰,或者类继承了一个被该注解修饰的类,JUnit将会使用这个注解所指明的运行器(runner)来运行测试,而不是JUnit默认的运行器。
package jtzen9.util;
 
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
 
@RunWith(Suite.class)
@Suite.SuiteClasses({TaskTest1.class,TaskTest2.class,TaskTest3.class})
public class SuiteTest {
    /*
     * 1.测试套件就是组织测试类一起运行的
     * 
     * 写一个作为测试套件的入口类,这个类里不包含其他的方法
     * 更改测试运行器Suite.class
     * 将要测试的类作为数组传入到Suite.SuiteClasses({})
     */
}
  运行结果:
  
  • 说明
        ①使用@RunWith注解,修改测试运行器。例如@RunWith(Suite.class),这个类就成为测试套件的入口类。
        ②@Suite.SuiteClasses()中放入测试套件的测试类,以数组的形式{class1,class2,......}作为参数。
 
  2. JUnit参数化设置
        如果测试代码大同小异,代码结构都是相同的,不同的只是测试的数据和预期值,那么有没有更好的办法将相同的代码结构提取出来,提高代码的重用度呢?
        解决:进行参数化测试。
        步骤:
  ①要进行参数化测试,需要在类上面指定如下的运行器:@RunWith (Parameterized.class)
  ②然后,在提供数据的方法上加上一个@Parameters注解,这个方法必须是静态static的,并且返回一个集合Collection。
  ③在测试类的构造方法中为各个参数赋值,(构造方法是由JUnit调用的),最后编写测试类,它会根据参数的组数来运行测试多次。
  • 代码: 

package jtzen9.util;
 
import static org.junit.Assert.*;
 
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)   //1.更改默认的测试运行器为RunWith(Parameterized.class)
public class ParameterTest {
    
    //2.声明变量存放预期值和测试数据
    int expected =0;
    int input1 = 0;
    int input2 = 0;
    
    //3.声明一个返回值 为Collection的公共静态方法,并使用@Parameters进行修饰
    @Parameters
    public static Collection<Object[]><object> data() {
        return Arrays.asList(new Object[][]{
                {3,1,2},
                {4,2,2}
        }) ;
    }
    
    //4.为测试类声明一个带有参数的公共构造函数,并在其中为之声明变量赋值
    public ParameterTest(int expected,int input1,int input2) {
        this.expected = expected;
        this.input1 = input1;
        this.input2 = input2;
    }
    
    //5.运行测试方法,即可完成对多组数据的测试
    @Test
    public void testAdd() {
        assertEquals(expected, new Calculate().add(input1, input2));
    }
}
  • 运行结果
  
 

以上是关于Java高级特性 第11节 JUnit 3.x和JUnit 4.x测试框架的主要内容,如果未能解决你的问题,请参考以下文章

Java高级特性 第1节 集合框架和泛型

Java高级特性 第7节 多线程

Java高级特性 第8节 网络编程技术

Java高级特性 第4节 输入输出流

Java高级特性 第6节 注解(初步认识)

Java高级特性 第14节 解析XML文档 - SAX 技术