Junit4测试执行(蓝桥课学习笔记)
1、Junit测试套件
实验介绍
在前面的章节中,我们一次都只执行一个测试类,但在实际的单元测试项目中测试类往往非常多,一个一个地执行显然不太现实。JUnit 提供的测试套件 ( Test Suite ) 可以将多个测试类组织在一起,批量运行多个测试类。本实验主要介绍 JUnit 4 中使用测试套件批量运行测试类。
知识点
- JUnit 4 中使用测试套件批量运行测试类
实验内容
第 1 步:新建两个测试类 ClassATest 和 ClassBTest 。
测试类 ClassATest 的具体代码如下:
import static org.junit.Assert.*;
import org.junit.Test;
public class ClassATest {
@Test
public void testA_1() {
System.out.println("这是测试类 ClassATest中的测试方法testA_1");
assertEquals(1 , 1);
}
@Test
public void testA_2() {
System.out.println("这是测试类 ClassATest中的测试方法testA_2");
assertEquals(1 , 1);
}
}
测试类 ClassBTest 的具体代码如下:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class ClassBTest {
@Test
public void testB_1() {
System.out.println("这是测试类 ClassBTest中的测试方法testB_1");
assertEquals(1 , 1);
}
@Test
public void testB_2() {
System.out.println("这是测试类 ClassBTest中的测试方法testB_2");
assertEquals(1 , 1);
}
@Test
public void testB_3() {
System.out.println("这是测试类 ClassBTest中的测试方法testB_3");
assertEquals(1 , 1);
}
}
被测试类准备好以后,下面来新建测试套件:
第 2 步:在包名上点击鼠标右键,在弹出的右键菜单中依次点击 “ New -> Other... ”,打开 “ New ” 窗口。
第 3 步:选择 “ JUnit Test Suite ”,点击【Next】按钮,打开 “ New JUnit Test Suite ”窗口。
“ New JUnit Test Suite ”窗口下方的 “ Test classes to include in suite ” 栏中显示了即将创建的测试套件中包含的测试类,默认全部选中,我们可以根据实际情况选择当前创建的测试套件中需要包含的测试类。
第 4 步:在 Name 中填写测试套件名称,选择测试套件中包含的测试类后,点击【Finish】按钮。
点击【Finish】按钮后创建了一个测试套件,并自动创建测试套件内容,测试套件代码如下。我们也可以直接创建一个 Java 类,手动编写测试套件的内容。
package test.com.lanqiao.Demo;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ ClassATest.class, ClassBTest.class })
public class AllTests {
}
代码解读:
1)创建一个使用 public 修饰的空类作为测试套件的入口,代码如下:
public class AllTests {
}
2)测试类需要添加 @RunWith 和 @SuiteClasses 注解才能进行批量测试。所以,在 Junit 的测试套件中,需要导入相应的库,代码如下:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
3)将 org.junit.runners.Suite 作为参数传入注解 @RunWith,告诉 JUnit 为此类使用套件运行器执行,代码如下:
@RunWith(Suite.class)
4)将需要放入测试套件的测试类组成数组作为注解 @SuiteClasses 的参数,这样测试套件运行时 @SuiteClasses 注解中所有的类都可以进行测试,而不需要分别执行测试。例如,在本例中,我们需要在测试套件中执行 ClassATest 和 ClassBTest 两个测试类,可以使用如下代码:
@SuiteClasses({ ClassATest.class, ClassBTest.class })
第 5 步:执行测试套件。点击 eclipse 工具栏中的【Run】按钮执行测试套件,本例的执行结果如下图所示:
从上图中可以看出:运行 AllTests 测试套件后,测试套件中的 ClassATest 和 ClassBTest 两个测试类中的测试方法都被执行了一次。
实验总结
在 JUnit 4 中可以将多个测试类组织在一起创建测试套件,批量运行多个测试类。JUnit 4 测试套件中不仅可以包含基本的测试类,还可以包含其它的测试套件,能非常方便地对不同模块的单元测试代码进行分层管理。如果存在多个测试套件,需要注意测试套件之间不能有循环包含关系,否则会出现死循环的情况。
2、自定义测试用例执行顺序
在使用 JUnit 执行测试用例时,有些测试用例需要按一定的顺序来执行。那么。JUnit 中的测试方法是按照什么顺序来执行的呢?我们先来看一个例子:下图左侧测试类中的五个测试方法是用 “ test + 数字 ” 命名的,并且是按数字的升序排列的。前后两次执行该测试类,执行结果如右侧的截图所示。从图中可以看出:两次的执行顺序都不相同,并且与代码编写的顺序也不相同,即:JUnit 中测试用例的执行顺序默认是随机的。
从 JUnit 4.11 开始的 JUnit 4.x 版本中,测试类的执行顺序可通过对测试类添加注解 “ @FixMethodOrder(annotation) ” 来指定。@FixMethodOrder(annotation) 注解的参数是 org.junit.runners.MethodSorters 对象,在枚举类 org.junit.runners.MethodSorters 中定义了三种注解类型:默认( MethodSorters.DEFAULT )、JVM( MethodSorters.JVM )、测试方法名( MethodSorters.NAME_ASCENDING )。
下面我们使用 eclipse 中默认的 JUnit 4.13 版本,针对下面的测试类分别使用三种注解类型来验证测试方法的执行顺序:
package com.lanqiao.Demo;
import org.junit.Test;
public class OrderTest {
@Test
public void test1() {
System.out.println("我是test1()");
}
@Test
public void test2() {
System.out.println("我是test3()");
}
@Test
public void test3() {
System.out.println("我是test3()");
}
@Test
public void test4() {
System.out.println("我是test4()");
}
@Test
public void test5() {
System.out.println("我是test5()");
}
}
注意:在使用 @FixMethodOrder 注解前需先导入 org.junit.FixMethodOrder 和 org.junit.runners.MethodSorters ,具体代码如下:
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
MethodSorters.DEFAULT(按默认的顺序执行)
注解格式:@FixMethodOrder(MethodSorters.DEFAULT)
使用该注解修饰的测试类执行时测试类中的测试方法会以确定但不可预期的顺序执行,即执行顺序不可预期,但每次执行的顺序都相同。这个顺序是由方法名的 hashcode 值来决定的,如果 hashcode 值大小一致则按方法名的字典顺序确定执行顺序。例如,下图中的类是按 " test + 数字 " 来命名的,多次执行这个测试类,执行顺序都相同,和方法的编写顺序一致。
例如,下图中的测试类,每次执行时的执行顺序都相同,但是与测试方法的编写顺序不一致,如下图所示:
另外 ,使用 @FixMethodOrder(MethodSorters.DEFAULT) 注解修饰的测试类在不同操作系统中执行时,可能会出现不一样的执行顺序,但在同一操作系统上,多次执行的顺序不变。
MethodSorters.JVM(按 JVM 返回的方法名顺序执行)
注解格式:@FixMethodOrder(MethodSorters.JVM)
使用该注解修饰的测试类执行时,测试类中的测试方法的执行顺序是不可预测的,即每次运行的顺序可能都不一样。例如,下图左侧是一个使用 @FixMethodOrder(MethodSorters.JVM) 注解修饰的测试类,右侧是分别执行三次的结果,从图中可以看出:每次执行的顺序都不相同。
MethodSorters.NAME_ASCENDING(按方法名的字母顺序执行)
注解格式:@FixMethodOrder(MethodSorters.NAME_ASCENDING)
使用该注解修饰的测试类执行时,测试类中的测试方法会按照方法名的字典顺序执行。由于是按字符的字典顺序,所以以这种方式指定的执行顺序会始终保持一致。不过,使用这种方式时测试方法的命名需遵循一定的规则才能让测试方法按照我们期望的顺序执行。例如,下图中的测试类执行时是按首字母的排序顺序执行的:
综上所述,@FixMethodOrder 注解的三种注解类型中,JVM 每次的执行顺序都是随机的,可能都不相同;DEFAULT 每次执行的执行顺序都是相同的,但是这个执行顺序是不可预期的,即没有明确的排序规则;NAME_ASCENDING 是按方法名的排序来执行的,每次执行顺序都相同,JUnit 4 中推荐使用该方法。
3、忽略测试
在单元测试过程中有时候会出现需要临时跳过部分测试类或测试方法的情况,比如,部分测试类或测试方法还未编写完成。JUnit 4 中提供的 @Ignore 注解可以帮助我们在执行单元测试时忽略不需要执行的测试类或方法,当需要暂时不执行特定的测试类或测试方法时可以使用 @Ignore 注解。
例如,在下图中的测试类中,测试方法 testCase1() 和 testCase2() 的 @Test 注解后面增加了 @Ignore 注解,在测试类执行结果中我们可以看到:测试类中共有 3 个测试方法,有两个被跳过没有执行( 2 skipped )。在下方的测试方法明细列表中可以看到:测试方法 testCase1() 和 testCase2() 显示为灰色,表示它们没有被执行。
@Ignore 注解除了可用来修饰测试方法,还可以用来修饰测试类,如果用来修饰测试类则整个测试类都不会执行。例如,下图中在测试类 CalculatorTest 上方添加了@Ignore 注解,执行结果中显示了 “ Runs: 1/1 ( 1 skipped ) ”的字样,表示该测试类被跳过,类中的所有测试方法都没有被执行。
4、异常测试
实验介绍
在单元测试过程中,除了验证代码在各种正常的场景下是否能够按预期实现相应的功能,还需要测试代码在异常情况下的处理是否正确。例如,对一个除法计算的方法进行测试时,需要测试输入的除数为 0 时是否会抛出如下图所示的 ArithmeticException(算术运算异常)异常;
数组越界时是否会抛出 ArrayIndexOutOfBoundsException(数组越界)异常。
本实验主要以 Junit 4.10 为例介绍 Junit 4.x 中异常测试的几种常见方法。
知识点
- 使用 @Test 注解中的 excepted 参数进行异常测试
- 使用 try / catch 语句进行异常测试
- 使用 ExpectedException 规则进行异常测试
实验内容
本实验将以 ArithmeticException(算术运算异常)为例介绍异常测试的方法,被测试类代码如下:
package com.lanqiao.Demo;
public class ExceptionTest {
public int division(int a, int b) {
return a / b ;
}
}
假设针对 ExceptionTest 类中的 division() 方法我们设计了如下异常测试用例(即除数为 0 时抛出 ArithmeticException 异常):
测试用例编号 | 测试对象 | 输入 | 预期结果 |
---|---|---|---|
testcase_01 | division() | a = 5 , b = 0 | 抛出 ArithmeticException 异常 |
使用 @Test 注解的 excepted 参数进行异常测试
JUNIT 4 中,用来修饰测试方法的 @Test 注解提供了一个追踪异常的可选参数 excepted ,可以用来测试代码执行过程中是否抛出了预期的异常信息。包含 excepted 参数的 @Test 注解格式为:@Test(expected=*.class) ,其中 expected 参数的值是一个异常的类型。
下面我们针对被测试类 ExceptionTest 的测试用例 testcase_01 ,使用 @Test 注解中的 excepted 参数进行异常测试。完整代码如下:
package test.com.lanqiao.Demo;
import org.junit.Test;
import com.lanqiao.Demo.ExceptionTest;
public class ExceptionTestTest {
ExceptionTest test = new ExceptionTest();
//在@Test注解中添加了expected参数用来测试是否按预期抛出异常
@Test(expected = ArithmeticException.class)
public void testDivision() {
test.division(5, 0);
}
}
代码解读:在测试方法的 @Test 注释后面直接添加 “ expected=异常类型.class ” 即可对该测试方法进行异常测试。例如,测试方法 testDivision() 的 @Test 注释后面添加了 (expected = ArithmeticException.class) ,可以验证测试方法执行时是否按照预期抛出了 ArithmeticException 异常。
在 eclipse 中执行该测试类。结果为测试通过,如下图所示。表明 division() 方法在传入除数为 0 的参数时正确地抛出了 ArithmeticException 异常,所以测试结果为通过。
如果我们将 division() 方法的入参改为非 0 的数字,此时代码不会抛出异常,则会断言失败,测试结果为失败,如下图所示:
使用 try ... catch 语句进行异常测试
在 JUnit 4 中,@Test 注解的 excepted 参数可以测试代码执行时是否抛出了预期的异常类型。但是,使用该方法无法测试异常中消息的值或引发异常后域对象的状态。如果想要验证异常中的信息是否符合预期,或是当前代码库不支持 lambdas ,也可以使用 JUnit 3 中常用的 try ... catch 语句进行异常测试。
例如,在本实验的被测试类 ExceptionTest 中,当 division() 方法中的除数为 0 时会抛出 ArithmeticException 异常,异常的 message 属性中包含 "/ by zero" ,如下图所示:
下面我们使用 try ... catch 语句对被测试方法 division() 进行异常测试,验证当除数为 0 时抛出的异常类型是否正确以及异常的 message 属性中是否包含期望的内容 "/ by zero" 。测试类 ExceptionTestTest 的完整代码如下:
package test.com.lanqiao.Demo;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import com.lanqiao.Demo.ExceptionTest;
public class ExceptionTestTest {
ExceptionTest test = new ExceptionTest();
@Test
public void testDivision() {
try {
test.division(5, 0);
//如果未抛出异常则断言失败
fail("测试失败:未按预期抛出异常!");
}catch (ArithmeticException e) {
//断言抛出的异常类型是否为ArithmeticException
assertTrue(e instanceof ArithmeticException);
//断言异常的message属性中是否包含/ by zero
assertTrue(e.getMessage().contains("/ by zero"));
}
}
}
代码解读:
1)使用 try ... catch 语句进行异常测试前先针对被测试类 ExceptionTest 创建测试类 ExceptionTestTest 并按照测试用例 testcase_01 编写测试方法 testDivision() :
package test.com.lanqiao.Demo;
import org.junit.Test;
import com.lanqiao.Demo.ExceptionTest;
public class ExceptionTestTest {
ExceptionTest test = new ExceptionTest();
@Test
public void testDivision() {
test.division(5, 0);
}
}
2)将原测试方法中的内容放入 try 代码块中,并在 try 代码块的末尾编写 assert.fail 语句。该语句用来判断代码执行时是否抛出了异常,如果未抛出异常则断言失败,测试方法执行失败。
@Test
public void testDivision() {
try {
test.division(5, 0);
//如果未抛出异常则断言失败
fail("测试失败:未按预期抛出异常!");
}catch() {
}
}
3)使用 assert.fail 断言前需要导入相应的类库 Assert.fail :
import static org.junit.Assert.fail;
4)在 catch 代码块中编写断言,判断执行测试方法时是否抛出异常及异常的内容,代码如下。其中:
- 第一个断言语句中使用了对象运算符 instanceof 判断抛出的异常对象是否属于 ArithmeticException 类或其子类的实例;
- 第二个断言语句中使用了 getMessage() 方法获取异常 message 属性中的内容,并判断 message 属性中是否包含预期的字符串 "/ by zero" 。
catch (ArithmeticException e) {
//断言抛出的异常类型是否为ArithmeticException
assertTrue(e instanceof ArithmeticException);
//断言异常的message属性中是否包含 / by zero
assertTrue(e.getMessage().contains("/ by zero"));
}
5)使用 assertTrue 方法进行断言前需导入相关的类库,具体如下:
import static org.junit.Assert.assertTrue;
在 eclipse 中执行测试类 ExceptionTestTest ,执行结果如下图所示:结果为通过,表明代码按照预期抛出了异常且异常内容与预期相符。
如果我们将测试方法中的入参进行修改,使除数不为 0 ,如,test.division(5, 1) ,此时执行代码时应该不会抛出 ArithmeticException 异常。修改后执行结果如下图所示,测试方法执行失败,原因为断言失败,并输出了 fail() 断言方法中的内容。
使用 ExpectedException 规则进行异常测试
除了以上两种异常测试方法,JUnit 4 中还提供了一种 ExpectedException 规则方式可以对程序抛出的异常类型及异常的 message 属性进行测试。使用该方法进行异常测试时,JUnit 可以为我们提供更详细的异常信息,更有利于错误的定位。
下面我们仍然以 division() 方法为例介绍如何使用 ExpectedException 规则进行异常测试,ExceptionTestTest 测试类的完整代码如下:
package test.com.lanqiao.Demo;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.junit.Test;
import com.lanqiao.Demo.ExceptionTest;
public class ExceptionTestTest {
ExceptionTest test = new ExceptionTest();
//使用@Rule注解定义一个ExpectedException规则
@Rule
public ExpectedException exceptedE = ExpectedException.none();
@Test
public void testDivision() throws ArithmeticException {
//断言抛出的异常类型是否为ArithmeticException
exceptedE.expect(ArithmeticException.class);
//断言异常的message属性内容
exceptedE.expectMessage("/ by zero");
test.division(5, 0);
}
}
代码解读:
1)使用 ExpectedException 规则前需先导入 @Rule 注释及 ExpectedException 相关的类库,具体如下:
import org.junit.Rule;
import org.junit.rules.ExpectedException;
2)@Rule 注解是 JUnit 中的规则注解,能够灵活地定义一些规则,其中 org.junit.rules.ExpectedException 可用于异常测试,进行异常测试时需要先使用 @Rule 注解创建一个 ExpectedException 规则变量。需要注意的是:@Rule 注解的 ExpectedException 变量必须声明为 public 。
@Rule
public ExpectedException exceptedE = ExpectedException.none();
3)ExpectedException 的 expect() 方法用于断言抛出的异常类型是否为 ArithmeticException(算术运算异常)。
exceptedE.expect(ArithmeticException.class);
4)ExpectedException 的 expectMessage()方法用于断言异常的 message 属性是否符合预期,即内容是否为 “ / by zero ” ;
5)需要特别注意的是:调用被测试方法 test.division(5, 0) 的语句必须写在异常断言的后面,否则无法按照预期执行测试方法。
执行测试类 ExceptionTestTest ,执行结果如下图所示,因为抛出的异常类型和 message 内容都与预期相符,所以测试结果为通过。
如果我们像前两种方法一样将测试方法中的入参进行修改,使除数不为 0 ,执行后结果如下图所示。从 Failure Trace 可以看出,使用 ExpectedException 规则与前两种方法相比,异常信息更加详细,更有助于问题的定位。
实验总结
本实验主要介绍了 JUnit 4.x 中常见的一些异常测试方法:
- 使用 @Test 注解的 excepted 参数进行异常测试只能断言异常的类型是否符合预期,无法判断异常的 message 属性是否符合预期;
- 使用 try...catch 代码块和 ExpectedException 规则这两种方法都可以断言异常类型及 message 属性是否符合预期;
- 使用 ExpectedException 规则进行异常测试时可以得到更详细的异常信息,更有助于错误的定位修改。
异常测试还有一些其他的方法,如预期异常规则等,有兴趣的读者也可以查阅相关资料进行了解。
5、超时测试
实验介绍
在单元测试过程中我们经常会需要验证测试方法是否在指定的时间内执行完成,以了解被测试方法的性能等情况。JUnit 4 中提供了两种超时测试方法:一种是使用 @Test 注解的 timeout 参数,另一种是使用 @Rule 注解中的 Timeout Rule 。本实验将通过一个实例介绍这两种超时测试方法。
知识点
- 使用 @Test 注解的 timeout 参数进行超时测试
- 使用 @Rule 注解的 Timeout Rule 进行超时测试
实验内容
本实验将以下面的 TimeoutTest 类为例介绍两种超时测试方法。为了更好地模拟超时的情况,被测试类中使用 sleep() 方法休眠了 5000 毫秒。
public class TimeoutTest {
public int add(int a, int b) {
//休眠5000毫秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return a + b + 1 ;
}
}
使用 @Test 注解的 timeout 参数进行超时测试
Junit 4 中的 @Test 注解用来修饰测试方法,该注解的 timeout 参数可用来约束测试方法运行的最长时间。该参数可以指定一个超时时间(单位为:毫秒)给测试方法,如果测试方法在的指定时间内没有运行完成则测试失败。timeout 参数需与 @Test 注解一起使用,格式为:@Test(timeout=xxx) ,其中 xxx代表超时时间,单位为毫秒。
例如,使用 @Test 注解的 timeout 参数对被测试类 TimeoutTest 中的 add() 方法进行超时测试的代码如下:
import static org.junit.Assert.*;
import org.junit.Test;
import com.lanqiao.Demo.TimeoutTest;
public class TimeoutTestTest {
TimeoutTest test = new TimeoutTest();
//@Test注解中设置了超时时间为1000毫秒
@Test(timeout = 1000)
public void test() {
assertEquals(6, test.add(2, 3));
}
}
代码解读:
@Test(timeout = 1000) 注解用来修饰测试方法 test() ,表示测试方法执行时会进行超时测试,超时时间为 1000 毫秒。
在 eclipse 中执行测试类 TimeoutTestTest ,执行完成后 Console视窗中显示 sleep interrupted 的异常信息,如下图所示:
Junit 视窗中显示测试类执行结果为失败,失败原因为 “ test timed out after 1000 milliseconds ”(执行时间超过 1000 毫秒),如下图所示:
继续使用刚才的测试类,将测试方法 @Test 注解的 timeout 参数的超时时间修改为 6000 毫秒,再次执行该测试类,执行结果如下图所示,测试类执行成功,没有出现超时异常。
使用 @Rule 注解的 Timeout Rule 进行超时测试
@Rule 注解中的 Timeout Rule 也可以用来完成超时测试,与 @Test 注解的 timeout 参数不同的是:Timeout Rule 的作用范围比 @Test 注解 timeout 参数的作用范围更广。 @Test 注解的 timeout 参数作用于测试方法,而 Timeout Rule 的作用范围为当前测试类的全部测试方法。
下面我们使用 Timeout Rule 的方法对被测试类 TimeoutTest 进行超时测试,测试类 TimeOutTestTest 的完整代码如下:
package test.com.lanqiao.Demo;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import com.lanqiao.Demo.TimeOutTest;
public class TimeOutTestTest {
// 定义Timeout规则
TimeOutTest test = new TimeOutTest();
@Rule
public Timeout testTimeOut = new Timeout(1000);
@Test
public void test() {
assertEquals(6, test.add(2, 3));
}
}
代码解读:
1)使用 Timeout Rule 前需先导入 Timeout Rule 相关的类库,具体如下:
import org.junit.Rule;
import org.junit.rules.Timeout;
2)定义 Timeout 规则变量,使用 @Rule 注解修饰,并定义超时时间(单位为:毫秒)。
@Rule
public Timeout testTimeOut = new Timeout(1000);
3)测试方法中不需要加其他注解,与正常的测试方法写法一致。
执行该测试类,执行结果为失败,失败原因为超时,如下图所示。如果该测试类中有多个测试方法,则该 Timeout 超时规则将应用于多个测试方法。
实验总结
JUnit 4 中可以使用 @Test 注解的 timeout 参数和 @Rule 注解的 Timeout Rule 两种方法进行超时测试。两者的区别在于作用范围:使用 @Test 注解的 timeout 参数时作用范围为当前测试方法,使用 Timeout Rule 时作用范围为当前测试类的所有测试方法。如果只需要针对测试类中的部分测试方法进行超时测试,可以使用 @Test 注解的 timeout 参数进行;如果需要对测试类中的所有测试方法进行超时测试,则可使用 @Rule 注解的 Timeout Rule 进行测试。