2.JUnit4使用(3)Junit测试方法介绍(二)

1,299 阅读8分钟

2.2.6 注释详解

Junit4 注解提供了书写单元测试的基本功能。在本节我们给大家介绍@BeforeClass, @AfterClass,@Before, @After 和@Test这几个基本注解。

 

@BeforeClass注解

被@BeforeClass注解的方法只被执行一次,是运行junit测试类时第一个被执行的方法。这样的方法被用作执行计算代价很大的任务,如打开数据库连接等。被@BeforeClass 注解的方法应该是静态的(即 static类型的).

 

@AfterClass注解

被@AfterClass注解的方法也只被执行一次,是运行junit测试类是最后一个被执行的方法。该类型的方法被用作执行类似关闭数据库连接的任务。被@AfterClass 注解的方法应该是静态的(即 static类型的).

 

@Before注解

被@Before 注解的方法是junit测试类中的任意一个测试方法执行前都会执行的方法,该类型的方法可以被用来为测试方法初始化所需的资源。

 

@After注解

被@After注解的方法是junit测试类中的任意一个测试方法执行后都会执行的方法, 即使被@Test 或 @Before修饰的测试方法抛出异常。该类型的方法被用来关闭由@Before注解修饰的测试方法打开的资源。

 

@Test 注解

被@Test注解的测试方法包含了真正的测试代码,并且会被Junit应用为要测试的方法。@Test注解有两个可选的参数:expected 表示此测试方法执行后应该抛出的异常,(值是异常名);timeout 检测测试方法的执行时间。

 

@Ignore注解

被@Ignore注解的测试方法包含了被忽略的测试代码,在测试过程中不会运行。

 

       我们通过一段测试代码来进行检验,首先新建一个JDemo类如下所示:

package com.mooctest.util;

 

class JDemo extends Thread {  

  

    int result;  

  

    public int add(int a, int b) {  

        try {  

            sleep(1000);  

            result = a + b;  

        } catch (InterruptedException e) {  

        }  

        return result;  

    }  

  

    public int division(int a, int b) {  

        return result = a / b;  

    }  

}  

 

package com.mooctest.util;

 

import static org.junit.Assert.*;  

  

import org.junit.*;  

  

public class JDemoTest {  

  

    //测试BeforeClass注解

    @BeforeClass  

    public static void setUpBeforeClass() throws Exception {  

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

    }  

  

    //测试AfterClass注解

    @AfterClass  

    public static void tearDownAfterClass() throws Exception {  

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

    }  

  

    //测试Before注解

    @Before  

    public void before() {  

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

    }  

    

    //测试After注解

    @After  

    public void after() {  

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

    }  

  

    //测试超时注解

    @Test(timeout = 10000)  

    public void testadd() {  

        JDemo a = new JDemo();  

        assertEquals(6, a.add(3, 3));  

        System.out.println("in Test ----Add");  

    }  

  

   //测试Test注解

    @Test  

    public void testdivision() {  

        JDemo a = new JDemo();  

        assertEquals(3, a.division(6, 2));  

        System.out.println("in Test ----Division");  

    }  

  

   //测试Ignore注解

    @Ignore  

    @Test  

    public void test_ignore() {  

        JDemo a = new JDemo();  

        assertEquals(6, a.add(1, 5));  

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

    }  

  

    //测试异常注解

@Test(expected=ArithmeticException.class)

public void testException(){

assertEquals(2,new Calculate().divide(6, 0));

}

 

   //测试Fail

    @Test  

    public void teest_fail() {  

        fail();  

    }  

}  

通过eclipse运行代码,我们在控制台应该可以得到如下的输出:

in BeforeClass================

in Before

in After

in Before

in Test ----Add

in After

in Before

in After

in Before

in Test ----Division

in After

in AfterClass=================

Junit的测试结果如图2.20所示:

image.png

图2.20  注解程序运行结果图

可以看到测试的add和division的方法都没有问题,因为我们正确抓取异常的缘故,所以测试异常的程序也成功运行,被ignore注解的方法被系统忽略了,只有fail被报错了。

2.2.7 Failure和Error

在Junit模块中有两种报错模式,一种是Failure还有一种是Error。Failure一般由单元测试使用的断言方法判断失败所引起的,这就表示测试点发现了问题,就是说程序输出的结果和我们预期的不一样。而Error是由代码异常引起的,它可以产生与测试代码本身的错误,也可以是被测试代码中的一个隐藏的bug。对于软件测试工作者而言,测试用例不是用来证明你是对的,而是用来证明你没有错。

以简单的运算为例,我们先建立一个简单的Calculate类,如下:

package com.mooctest.util;

 

public class Calculate {

  public int add(int a,int b){

  return a+b;

  }

    

  public int divide(int a,int b){

  return a/b;

  }

}

  针对这个类,我们新建一个专门测试Failure和Error的测试用例,如下:

package com.mooctest.util;

 

import static org.junit.Assert.*;

 

import org.junit.Test;

 

import com.mooctest.util.Calculate;

 

public class ErrorAndFailureTest {

//测试错误,报错Failure

@Test

public void testAdd(){

assertEquals(5,new Calculate().add(3, 3));

}

          //测试失败,报错Error

@Test

public void testDivide(){

assertEquals(2,new Calculate().divide(6, 0));

}

}

2.2.8 参数化测试

Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同一个测试,因为针对同一个测试方法,不同的参数值会产生不同的结果,那么我们为了测试全面,会把多个参数值都写出来并进行断言测试。参数化测试就好比把一个“输入值,期望值”的集合传入给测试方法,达到一次性测试的目的。

你将遵循 5 个步骤来创建参数化测试:

1.用 @RunWith(Parameterized.class) 来注释 test 类。

2.创建一个由 @Parameters 注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合。

3.创建一个公共的构造函数,它接受和一行测试数据相等同的东西。

4.为每一列测试数据创建一个实例变量。

5.用实例变量作为测试数据的来源来创建你的测试用例。

       我们用一个简单的斐波那契数列的类作为例子,首先新建一个Fibonacci类,如下:

package com.mooctest.util;

 

class Fibonacci {  

  

    public static int compute(int input) {  

        int result;  

        switch (input) {  

        case 0:  

            result = 0;  

            break;  

        case 1:  

        case 2:  

            result = 1;  

            break;  

        case 3:  

            result = 2;  

            break;  

        case 4:  

            result = 3;  

            break;  

        case 5:  

            result = 5;  

            break;  

        case 6:  

            result = 8;  

            break;  

        default:  

            result = 0;  

        }  

        return result;  

    }  

}  

      针对这个函数我们新建一个测试用例来进行参数化测试,新建测试用例FibonacciTest如下:

package com.mooctest.util;

 

import static org.junit.Assert.*;  

  

import java.util.Arrays;  

  

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 FibonacciTest {  

  

    @Parameters(name = "{index}: fib({0})={1}")  

    public static Iterable<Object[]> data() {  

        return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },  

                { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });  

    }  

  

    private int input;  

    private int expected;  

  

    public FibonacciTest(int input, int expected) {  

        this.input = input;  

        this.expected = expected;  

    }  

  

    @Test  

    public void test() {  

        assertEquals(expected, Fibonacci.compute(input));  

    }  

}  

 

运行成功,Junit测试结果如图2.21所示:

image.png

图2.21  参数化测试结果图

2.2.9 打包测试

当我们面对较大的测试项目时,往往在项目中会有很多个测试用例,如果一个个测试显得很麻烦,因此出现了打包测试这个功能。顾名思义,打包测试就是一次性测试完成包中含有的所有测试用例。

以我们之前完成的3个测试用例CalculateTest、FibonacciTest以及JDemoTest为例,我们创建一个打包测试来对他们进行测试,代码如下:

package com.mooctest.util;

 

import static org.junit.Assert.*;

 

import org.junit.AfterClass;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.junit.runners.Suite;

import org.junit.runners.Suite.SuiteClasses;

 

 

@RunWith(Suite.class)

@SuiteClasses({CalculateTest.class,FibonacciTest.class, JDemoTest.class})

 

public class SuiteTest {

 

}

当打包测试运行完成后,打包运行结果与单独运行结果相同,具体运行结果如图2.22所示:

image.png

图2.22  打包测试运行结果图

2.2.10 异常处理

单元测试主要是被开发人员用来验证代码,很多时候单元测试可以检查抛出预期异常(expected exceptions)的代码。面对异常,我们需要给出正确的异常处理方法。在Java语言中,JUnit是一套标准的单元测试方案,它提供了很多验证抛出的异常的机制。

我们拿下面的代码作为例子,写一个测试,确保canVote() 方法返回true或者false, 同时你也能写一个测试用来验证这个方法抛出的IllegalArgumentException异常。

public class Student {

  public boolean canVote(int age) {

      if (i<=0) throw new IllegalArgumentException("age should be +ve");

      if (i<18) return false;

      else return true;

  }

}

检查抛出的异常有三种方式,它们各自都有优缺点:

 

1.@Test(expected…)

@Test注解有一个可选的参数,”expected”允许你设置一个Throwable的子类。如果你想要验证上面的canVote()方法抛出预期的异常,我们可以这样写:

@Test(expected = IllegalArgumentException.class)

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    student.canVote(0);

}

这种检查异常的方式直观明了,缺点是这个测试有一点误差,因为异常会在方法的某个位置被抛出,但不一定在特定的某行。

 

2.ExpectedException

在使用JUnit框架中的ExpectedException类前,需声明ExpectedException异常。

 

@Rule

public ExpectedException thrown= ExpectedException.none();

然后你可以使用更加简单的方式验证预期的异常。

 

@Test

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    thrown.expect(IllegalArgumentException.class);

    student.canVote(0);

}

或者可以设置预期异常的属性信息。

 

@Test

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    thrown.expect(IllegalArgumentException.class);

    thrown.expectMessage("age should be +ve");

    student.canVote(0);

}

除了可以设置异常的属性信息之外,这种方法还有一个优点,它可以更加精确的找到异常抛出的位置。在上面的例子中,在构造函数中抛出的未预期的(unexpected) IllegalArgumentException 异常将会引起测试失败,我们希望它在canVote()方法中抛出。

3.Try/catch with assert/fail

在JUnit4之前的版本中,使用try/catch语句块检查异常

 

@Test

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    try {

        student.canVote(0);

    } catch (IllegalArgumentException ex) {

        assertThat(ex.getMessage(), containsString("age should be +ve"));

    }

    fail("expected IllegalArgumentException for non +ve age");

}

尽管这种方式很老了,不过还是非常有效的。主要的缺点就是很容易忘记在catch语句块之后需要写fail()方法,如果预期异常没有抛出就会导致信息的误报。我曾经就犯过这样的错误。

总之,这三种方法都可以测试预期抛出的异常,各有优缺点。大家可以根据自己的喜好进行选择使用。

 

2.2.11 限时测试

在我们进行代码开发的时候,经常会遇到死循环或者方法运行时间过长,为了防止出现死循环或者检查方法效率,我们就需要使用到限时测试,即超出设定时间即视为测试失败,共有两种写法。

第一种写法如下所示:

@Test(timeout=1000)  

public void testWithTimeout() {  

  ...  

}  

在Test注解之后加上一个测试的实现,比如说一千毫秒。

 

第二种写法则和异常处理的第二种方法类似,具体写法如下:

public class HasGlobalTimeout {  

    public static String log;  

  

    @Rule  

    public Timeout globalTimeout = new Timeout(10000); // 10 seconds max per method tested  

  

    @Test  

    public void testInfiniteLoop1() {  

        log += "ran1";  

        for (;;) {  

        }  

    }  

  

    @Test  

    public void testInfiniteLoop2() {  

        log += "ran2";  

        for (;;) {  

        }  

    }  

}