JUnit是一个基于Java语言的单元测试框架。 本文以JUnit4作为基准版本进行JUnit的使用示意:
1. JUnit简介
JUnit是一个基于Java语言的单元测试框架,它是由Kent Beck和Erich Gamma编写的一个回归测试框架(regression testing framework),逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。
JUnit有它自己的JUnit扩展生态圈,多数Java的开发环境(IDE)都已经集成了JUnit作为单元测试的工具。
Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。
Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
JUnit官网: https://junit.org/ 。
当前最新版本的JUnit框架是JUnit5: https://junit.org/junit5/ 。
而当前普遍广泛使用的版本是JUnit4: https://junit.org/junit4/ 。
2. JUnit在项目中的引入及使用
2.1. JUnit框架的引入
在项目Build Path中引入JUnit工具包(jar包
)或直接引用maven-depenency
,如:
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
2.2. 创建测试类
假设当前有一个待测试的类Calculator.class
:
public class Calculator {
/**
* 传入两个参数,求和
* @param a
* @param b
* @return
*/
public int add(int a,int b){
return a+b;
}
/**
* 传入两个参数,求差
* @param a
* @param b
* @return
*/
public int sub(int a,int b){
return a-b;
}
}
在这一步中,我们将为它创建一个名为CalculatorTest.class
的测试类:
public class CalculatorTest {
}
2.3. 根据业务代码编写无参、无返回值的测试方法
public class CalculatorTest {
@Test
//测试 add()方法
public void testAdd(){
Calculator c = new Calculator();
int result = c.add(1, 2);
Assert.assertEquals(result, 3);
}
@Test
//测试 sub()方法
public void testSub(){
Calculator c = new Calculator();
int result = c.sub(2, 1);
Assert.assertEquals(result, 1);
}
}
【注】测试方法必须是public方法且无返回值(void),即公共、无返回数据。
测试方法可以抛出异常。
2.4. 运行JUnit Test
以Eclipse-IDE为例:
运行完成后,即可显示Test结果:
3. JUnit的常用注解释义
Junit中包含如下常用的注解:
注解 | 标记位置 | 注解说明 | 注解内部属性 |
@Test | 方法 | 在JUnit4及以上版本,定义一个测试方法只需在方法前加上@Test即可。 【注】@Test测试方法必须是public、void方法,即公共、无返回数据。@Test方法允许抛出异常。 |
expected=XXException.class:如果程序按照当前测试方法进行,抛出XXException.class异常,则该测试方法通过。 timeout=xxx:如果当前测试方法在xxx毫秒之内完成,则测试通过,反之测试不通过。 |
@Ignore | 方法 | 被忽略的测试方法:跳过被标记的测试方法,不运行 | - |
@Before | 方法 | 在当前测试类中,每一个测试方法在运行之前均运行一次@Before标记的方法。 主要用于一些独立于用例之间的准备工作,如:准备一条测试数据等。 【注】@Before方法必须是public、void方法且不能为static。被标注方法不止运行一次,根据整个测试类的测试用例数而定。 |
- |
@After | 方法 | 在当前测试类中,每一个测试方法在运行完成后均运行一次@After标记的方法。 与@Before对应,主要用于部分独立用例完成之后的清理,如:移除一条测试数据等。 【注】@After方法必须是public、void方法且不能为static。被标注方法不止运行一次,根据整个测试类的测试用例数而定。 |
- |
@BeforeClass | 方法 | 在当前测试类中,在开始执行当前测试类的所有测试方法(@Test) 执行之前运行一次@BeforeClass标记的方法。 一般用于做一些测试前的准备工作,如:创建数据库连接、读取文件等。 【注】@BeforeClass方法必须是public、static、void方法,即公开、静态、无返回。这个被标记的方法只会运行一次。 |
- |
@AfterClass | 方法 | 在当前测试类中,当前测试类的所有测试方法(@Test) 执行完成之后运行一次@AfterClass标记的方法。 一般用于处理一些测试后续工作,例如清理数据,恢复现场。 【注】@AfterClass方法必须是public、static、void方法,即公开、静态、无返回。这个被标记的方法只会运行一次。 |
- |
@RunWith(<运行器类>)运行器类> | 测试类 | 置于测试类名之前,用于确定这个测试类将如何的。不标注会使用默认运行器,如:@RunWith(JUnit4.class)。 测试运行器则决定了用什么方式或偏好去运行这些测试集/类/方法。 |
常见的运行器有: @RunWith(Parameterized.class)参数化运行器:配合@Parameters使用JUnit的参数化功能; @RunWith(Suite.class)或 @SuiteClasses({ATest.class,BTest.class,CTest.class}): 测试集运行器配合使用测试集功能; @RunWith(JUnit4.class): JUnit4的默认运行器; @RunWith(JUnit38ClassRunner.class): 用于兼容junit3.8的运行器; 其他运行器,如: @RunWith(SpringJUnit4ClassRunner.class)集成了Spring的测试功能。 |
@Parameters(JUnit4及更早版本) | 方法 | 用于使用参数化测试功能。使用@Parameters注解的测试类必须指定@RunWith(Parameterized.class)运行器来进行修饰。 【注】@Parameters修饰的方法必须是public且为无参的static方法,并且必须返回Collection类型的结果。 示例代码:
|
JUnit5的增设了更多注解,详见官方文档: https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations 。
4. JUnit的断言解析
断言(Assert
)是编写测试用例的核心实现方式,即期望值与实际测试的比对,以此来判断测试是否通过。
更详细的断言说明,详见官方文档: https://github.com/junit-team/junit4/wiki/Assertions 或 https://github.com/junit-team/junit4/wiki/Assertions-CN 。
断言方法 | 说明 |
assertArrayEquals(expecteds, actuals) | 两个数组相等返回true |
assertEquals(expected, actual) | 两个对象等值(类似于equals)返回true |
assertNotEquals(expected, actual) | 与assertEquals对应,两个对象是不等值返回true |
assertNull(object) | 对象为空返回true |
assertNotNull(object) | 与assertNull对应,对象为非空返回true |
assertSame(expected, actual) | 两个对象的引用相等(类似于==)返回true |
assertNotSame(unexpected, actual) | 两个对象的引用不相等(类似于!=)返回true |
assertTrue(condition) | 查看运行结果是否为true |
assertFalse(condition) | 查看运行结果是否为false |
assertThat(actual, matcher) | 查看实际值是否满足指定的条件,满足则返回true。 assertThat一般会搭配allOf/anyOf/containsString/closeTo/hasItem/...等匹配符来使用。 例: @Test public void assertWithHamcrestMatcher() { assertThat(calculator.subtract(4, 1), is(equalTo(3))); } @Test public void testAssertThatBothContainsString() { assertThat("albumen", both(containsString("a")).and(containsString("b"))); } @Test public void testAssertThatHasItems() { assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three")); } @Test public void testAssertThatEveryItemContainsString() { assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n"))); } 关于assertThat的匹配符的使用,可对照官方文档说明: |
fail() | 强制测试失败。 在JUnit4之前的版本中,fail()通常配合try/catch代码块来使用,对于没有抛出预期异常的代码,test case的运行结果应该为fail。例: @Test public void test_vertex_args() throws GrammarException, VertexNotExistException, MismatchException, WeightException, LabelException { try { GraphPoetFactory().creatGraph("test/file/GraphPoet_vertex_args.txt"); } catch (GrammarException ex) { assertThat(ex.getMessage(), containsString("The vertex args is lack.")); } fail("expected for GrammarException of The vertex args is lack."); } 这与希望测试异常的结果一致:只有抛出预期异常,测试才算运行成功。 这种方法比较老,在JUnit4中,同样的目的可以用@Test(expected=Throwable.class)或ExpectedException的方式来替代。 |
JUnit5的断言提供了更多选择,详见其官方文档: https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions 。
5. JUnit测试类的几项编写原则
- 测试方法必须使用
@Test
进行修饰。 - 测试方法必须使用
public
、void
进行修饰,且不能带有任何参数。 - 测试代码位置应与业务代码的位置分离,如Maven项目中的:
src/test/java
。 - 测试类的包名称应该与和被测试类保持一致。
- 测试单元中的每个方法必须可以独立运行,测试方法间不能有任何依赖。
- 测试类建议使用
<业务类名称>Test.class
作为类名后缀。 - 测试方法建议使用
test
作为方法前缀。
6. JUnit使用的一些总结
6.1. JUnit测试异常的方法汇总
假设我们有一段业务代码如下:
public class Student {
public boolean canVote(int age) {
if (i<=0)
throw new IllegalArgumentException("age should be +");
if (i<18)
return false;
else
return true;
}
}
6.1.1. @Test(expected=Throwable.class)
这种方法代码更整洁清晰。不足的是:这个测试会有一点误差,因为异常会在方法的某个位置被抛出,但不一定在特定的某行;同时没有办法检查抛出异常的信息。
测试代码示例:
@Test(expected = IllegalArgumentException.class)
public void canVoteExp() {
new Student().canVote(0);
}
6.1.2. ExpectedException
这种方法除了可以设置异常的属性信息之外,还有一个优点:可以更加精确高效地定位到异常抛出的位置。
测试代码示例:
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void canVoteExp() {
Student student = new Student();
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("age should be +");
student.canVote(0);
}
6.1.3. Try/catch with assert/fail
这种方法相对比较老,不建议过多使用。
测试代码示例:
@Test
public void canVoteExp() {
Student student = new Student();
try {
student.canVote(0);
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("age should be +"));
}
fail("expected IllegalArgumentException for non + age");
}