欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java单元测试工具 Junit4入门使用教程

程序员文章站 2024-01-18 22:01:58
...

1.序言

JUnit是一个开源的Java单元测试框架,它由Kent Beck和Erich Gamma建立,JUnit4框架中运用了Java5的Annotation接口简化测试用例的编写,JUnit测试框架将测试更加便捷和容易,编写测试代码也是简单、明了,功能强大。

2.前期准备

工具准备:
(1)IntelliJ IDEA(用于创建Java 项目)
(2)安装配置好JDK 1.8
(3)安装配置好Maven

3.创建工程,添加包依赖

  • 使用Intellij IDEA 快速新建一个Maven构建的工程
    Java单元测试工具 Junit4入门使用教程
  • 在项目工程文件pom.xml中添加Junit4、hamcrest-core、hamcrest-library库依赖
    Java单元测试工具 Junit4入门使用教程

4.JUnit代码实战演示

4.1 实战第一步:

新建编写一个Calculator类,这是一个能够简单实现加减乘除、平方、开方的计算器类,然后对这些功能进行单元测试,这个类并不是很完美,预先故意保留了一些Bug用于演示,代码详情及注释如下:

public class Calculator {
    private static int result; // 静态变量,用于存储运行结果
    public void add(int n) {
        result = result + n;  //加法算法
    }
    public void substract(int n) {
        result = result - 1;  //预留Bug: 正确的应该是 result =result-n
    }
    public void multiply(int n) {
    	// 此方法暂未实现,用于备用
    }         
    public void divide(int n) {
        result = result / n;  
    }
    public void square(int n) {
        result = n * n;
    }
    public void squareRoot(int n) {
        for (; ;) ;            //预留Bug : 死循环
    }
    public void clear() {     // 将result结果清零
        result = 0;
    }
    public int getResult() {  //获取最终result的值
        return result;
    }
}

4.2 实战第二步

生成JUnit测试框架:用右键点击类Calculator弹出菜单,并选择Go to–>Test,勾选如下图所示:
Java单元测试工具 Junit4入门使用教程
点击后选择Create Test,系统会自动列出你这个类中包含的方法,选择你要进行测试的方法。此例中,我们将对“加、减、乘、除”四个方法进行测试,如下图:
Java单元测试工具 Junit4入门使用教程
之后系统会自动生成一个新类CalculatorTest,同时自动生成一些空的测试用例,接下来我们要修改我们这些测试用例,修改后结果如下:

package com.wmin.JUnit4TestDemo;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import static org.junit.Assert.*;

public class CalculatorTest {
    private static Calculator calculator=new Calculator();
    @Before
    public void setUp() throws Exception {
        calculator.clear();
    }
    @Test     //@Test表示自己要测试的方法
    public void testAdd() {
        calculator.add(2);
        calculator.add(3);
        System.out.println("输出加法运算结果:"+calculator.getResult());
        assertEquals(5, calculator.getResult());
    }

    @Test
    public void testSubstract() {
        calculator.add(10);
        calculator.substract(2);
        System.out.println("输出加法与减法混合后的运算结果:"+calculator.getResult());
        assertEquals(8, calculator.getResult());
    }
    @Ignore("Multiply() Not yet implemented")
    @Test
    public void testMultiply() {
        System.out.println("乘法运算方法未实现!");  //被忽略的方法将不会执行
    }

    @Test
    public void testDivide() {
        calculator.add(8);
        calculator.divide(2);
        System.out.println("输出加法与除法混合后的运算结果:"+calculator.getResult());
        assertEquals(4, calculator.getResult());
    }
}

4.3 实战第三步

运行测试代码,右键点击CalculatorTest测试类,选择“Run As–>Junit Test”,运行结果图如下;
控制台Junit窗口结果如下:
Java单元测试工具 Junit4入门使用教程

4.4 实战结果分析

(1)观察步骤4.3中的JUnit运行结果,右上角红色进度条表示表示发现出现错误,表示存在测试案例未通过,如下图,执行了4个测试案例,一个失败,一个忽略;
(2)JUnit输出控制窗口左侧记录每个测试案例的执行结果以及执行消耗时间,右侧窗口
显示测试未通过的测试案例;
(3)测试未通过案例分析:点击不通过案例,输出结果如下:

输出加法与减法混合后的运算结果:9
java.lang.AssertionError: 
Expected :8
Actual   :9

表示测试案例期望值为“8”,但实际运行结果为“9”,测试未通过;
分析testSubstract() 方法:

 @Test
    public void testSubstract() {
        calculator.add(10);
        calculator.substract(2);
        System.out.println("输出加法与减法混合后的运算结果:"+calculator.getResult());
        assertEquals(8, calculator.getResult());
}

其中Calculator对象分别调用add(10)和sunbstract(2)方法,预期结果result执行这两个方法后为8,实际因为substract方法存在Bug,导致实际结果在执行减法后result值为9,Bug如下:

 public void substract(int n) {
        result = result - 1;  //预留Bug: 正确的应该是 result =result-n
 }

5.JUnit4使用教程中级篇

通过JUnit代码实战演练,我们可认识到JUnit 4的基本用法,下面我来熟悉一下第四章节中使用到的Junit4框架及关键字等知识

5.1 引入必要的包

在测试类中用到了JUnit4框架,自然要把相应地Package包含进来。最主要地一个Package就是org.junit.*。把它包含进来之后,绝大部分功能就有了。还有一句话也非常地重要“import static org.junit.Assert.*;”,我们在测试的时候使用的一系列assertEquals方法就来自这个包。大家注意一下,这是一个静态包含(static),是JDK5中新增添的一个功能。也就是说,assertEquals是Assert类中的一系列的静态方法,一般的使用方式是Assert. assertEquals(),但是使用了静态包含后,前面的类名就可以省略了,使用起来更加的方便。

5.2 测试类的声明

大家注意到,我们的测试类是一个独立的类,没有任何父类。测试类的名字也可以任意命名,没有任何局限性。所以我们不能通过类的声明来判断它是不是一个测试类,它与普通类的区别在于它内部的方法的声明,我们接着会讲到。

5.3 创建一个待测试的对象

你要测试哪个类,那么你首先就要创建一个该类的对象。正如上一篇文章中的代码:

private static Calculator calculator = new Calculator();

为了测试Calculator类,我们必须创建一个calculator对象。

5.4 测试方法的声明

在测试类中,并不是每一个方法都是用于测试的,你必须使用“标注”来明确表明哪些是测试方法。“标注”也是JDK5的一个新特性,用在此处非常恰当。我们可以看到,在某些方法的前有@Before、@Test、@Ignore等字样,这些就是标注,以一个“@”作为开头。这些标注都是JUnit4自定义的,熟练掌握这些标注的含义非常重要。

5.5 编写一个简单的测试方法

首先,你要在方法的前面使用@Test标注,以表明这是一个测试方法。对于方法的声明也有如下要求:名字可以随便取,没有任何限制,但是返回值必须为void,而且不能有任何参数。如果违反这些规定,会在运行时抛出一个异常。至于方法内该写些什么,那就要看你需要测试些什么了

例如下方的测试案例

public void testAdd() {
      calculator.add(2);
      calculator.add(3);
      System.out.println("输出加法运算结果:"+calculator.getResult());
      assertEquals(5, calculator.getResult());
}

想要测试计算加法的方法是否正确,调用两次add方法,result初始值为0,最终结果为5,如果预期结果与实际结果一致,就通过,反之,测试不通过。
assertEquals(5, calculator.getResult())方法中,第一个参数填写期望结果值,第二个参数填写实际结果值,JUnit会自动比对期望结果与实际结果是否相等并反馈给用户。

5.6 忽略未完成的测试方法

如果一个方法的测试用例已经写好,但是方法体并未实现,在执行测试的时候,一定会失败,但这并不是真正的失败,JUnit提供了一种方法来区别他们,那就是在这种测试函数的前面加上@Ignore标注,表示此方法暂时不参与测试,挂起处理。

5.7 Fixture用法

Fixture的含义就是“在某些阶段必然被调用的代码”。比如我们上面的测试,由于只声明了一个Calculator对象,他的初始值是0,但是测试完加法操作后,他的值就不是0了;接下来测试减法操作,就必然要考虑上次加法操作的结果,因此我们需要再所有测试用例执行前清空其他干扰,@Before的作用就是使所有测试用例执行前,都必须执行的一个“前置”步骤,同理还有@After,表示所有测试用例执行完毕后都要执行@After中的收尾工作。

6.JUnit4 使用教程高级篇

6.1 测试函数执行效率

6.2 高级Fixture

第5.7章节讲述@Before和@After方法,但是@Before和@After是在测试类中每一个测试用例方法之前和之后都要执行一篇,遇到复杂的程序或耗时较长的程序时,比较费时间,JUnit提供了@BeforeClass和@AfterClass方法,表示每个测试类只在初始化时执行@BeforeClass当中的内容,当所有测试用例执行完毕之后,再执行@AfterClass方法
例如:
@BeforeClass
public void setUp() throws Exception { //异常判断
calculator.clear();
}
则表示方法在类开始时执行一次,后续每个TestCase执行时,不在执行这个方法。
注意:每个测试类只能有一个方法被标注为@BeforeClass或@AfterClass,并且该方法必须是 Public和Static的

6.3 限时测试

如果一个测试案例执行时间较长,或者出现死循环不停执行时,我们需要控制测试案例执行时间,只需在@Test中添加时间参数,Timeout 参数表明了你要设定的时间,单位为毫秒
例如下面高级测试类AdvancedTest.java中

package JunitTest;

import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

public class AdvancedTest {
	
	private static Calculator calculator = new Calculator(); //创建一个Calculator对象

	@Before
	public void setUp() throws Exception {
		calculator.clear();
	}

	@Test(timeout=2000)
	public void testSquareRoot() {
		//fail("Not yet implemented");
		calculator.squareRoot(4);
		assertEquals(2, calculator.getResult());
	}
}

因原始类Calculater中的SquareRoot()方法存在无限循环缺陷,设置testSquareRoot()方法超时参数后,方法执行2秒后即停止,控制台输出结果如下:

6.4 异常测试

JAVA中的异常处理也是一个重点,因此你经常会编写一些需要抛出异常的函数。那么,如果你觉得一个函数应该抛出异常,这当然是Bug ,并且JUnit也考虑到了这一点,来帮助我们找到这种 Bug 。例如,我们写的计算器类有除法功能,如果除数是一个0,那么必然要抛出“除以0异常”类似的提示。因此,我们很有必要对这些进行测试。

@Test(expected=ArithmeticException.class )
	 //@Test
    public   void  divideByZero()  {
 
    calculator.divide( 0 ); 
 
   } 

6.5 Runner( 运行器 )

当你把测试代码提交给JUnit框架后,框架如何来运行你的代码呢?答案就是Runner 。在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。

运行器使用@RunWith标注,并且把你所指定的 Runner 作为参数传递给它。另外一个要注意的是, @RunWith 是用来修饰类的,而不是用来修饰函数或者方法的。如果不指定Runner,则会使用系统默认的TestClassRunner,例如下方的两个代码效果是一样的
public class CalculatorTest {

}

@RunWith(TestClassRunner. class )
public class CalculatorTest {

}

6.6 参数化测试

在进行单元测试案例设计时,我们对同一方法的参数取值可能会存在多种,例如Calculater.java中求平法这个方法,参数取值为整数类型,取值可能有正数、负数、0三种情况,测试时我们可以写三个测试案例,三个测试方法对这个求平方的方法进行校验,如下所示:
public void square1() {

}
public void square2() {

}
public void square3() {

}
如果要测试的方法中,参数取值有7、8种甚至十几种,这种方式写测试案例显示不行,为了简化类似的测试, JUnit4 提出了“参数化测试”的概念,只写一个测试函数方法,把这若干种情况作为参数传递进去,一次性的完成所有输入参数的测试。代码如下:

package JunitTest;

import  static  org.junit.Assert.assertEquals;
import  org.junit.Test;
import  org.junit.runner.RunWith;
import  org.junit.runners.Parameterized;
import  org.junit.runners.Parameterized.Parameters;
 
import  java.util.Arrays;
import  java.util.Collection;

@RunWith(Parameterized. class )

public class SquareTest{

     private static Calculator calculator  =  new  Calculator();

     private  int param;
     private  int result;

    @Parameters
     public  static Collection data(){
         return  Arrays.asList(new Object[][] {
                 {2,  4},
                 {0,  0},
                 {-3,-9},
         });
    } 

// 构造函数,对变量进行初始化 
     public  SquareTest(int param,  int result){
         this .param  = param;
         this .result = result;
    } 
 
    @Test
     public void square(){
        calculator.square(param);
        assertEquals(result, calculator.getResult());
    } 
} 

参数化测试操作步骤及注意事项:
(1)参数化测试时,你要为这种测试专门生成一个新的类,而不能与其他测试共用同一个类;
(2)然后,你要为这个类指定一个Runner,而不能使用默认的Runner了,因为特殊的功能要用特殊的Runner嘛。@RunWith(Parameterized.class)这条语句就是为这个类指定了一个ParameterizedRunner。
(3)定义一个待测试的类,并且定义两个变量,一个用于存放参数,一个用于存放期待的结果。接下来,定义测试数据的集合,也就是上述的data()方法,该方法可以任意命名,但是必须使用@Parameters标注进行修饰,注意其中的数据,是一个二维数组,数据两两一组,每组中的这两个数据,一个是参数,一个是你预期的结果,比如我们的第一组{2, 4},2就是参数,4就是预期的结果。这两个数据的顺序前后任意,但是下一步构造方法参数要跟这个顺序匹配;
(4)构造函数,其功能就是对先前定义的两个参数进行初始化。 在这里你可要注意一下参数的顺序了,要和上面的数据集合的顺序保持一致。如果前面的顺序是{参数,期待的结果},那么你构造函数的顺序也要是“构造函数(参数, 期待的结果)”,反之亦然。最后就是写一个简单的测试案例。

6.7 打包测试

实际项目中,我们会写出很多很多个测试类,如果这些测试类必须一个一个的执行,也是比较麻烦的事情;JUni为我们提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大的方便了我们的测试工作。具体代码如下:

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

@RunWith(Suite. class )
@Suite.SuiteClasses( {
        CalculatorTest. class ,
        SquareTest. class 
        } )
public   class  AllCalculatorTests  {
  .......
} 

注意事项:
(1)打包测试需要一个特殊的Runner,在@RunWith标注传递一个参数Suite.class;
(2)同时,我们还需要另外一个标注@Suite.SuiteClasses ,来表明这个类是一个打包测试类,我们把需要打包的类作为参数传递给该标注就可以了,如上述例子中,将CalculatorTest和SquareTest两个类打包传递过去。
(3)打包测试类任意取名就行,类里面的内容为空即可。

7.相关源码

https://github.com/AnnWu/JUnit4TestDemo