目录
一、快速入门
1.1、引入依赖
1.2、第一个示例
1.3、注意事项
1.4、约定俗成的规则
1.5、打印输出与断言
2.1、抛出问题
2.2、junit AOP
三、其他拓展
3.1、超时设置
3.2、忽略某个单元测试
3.3、预期异常
四、总结
一、快速入门
本文内容以Junit4为主。
1.1、引入依赖
下面是junit4最新的jar包依赖
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
1.2、第一个示例
最简单的示例,就是使用一个@Test注解
package cn.ganlixin.junit;
import org.junit.Test;
public class FirstExample {
@Test
public void sayHello() {
System.out.println("hello world");
}
}
运行上面的sayHello方法,会输出:hello world。
1.3、注意事项
当我们进行测试的时候,只需要使用@Test注解需要测试的方法即可,但是被注解的方法必须要满足一下几个要求:
1、访问级别为public;
2、返回类型为void;
3、方法名随意,但是不能接收参数;
4、可以在在方法签名上抛出异常,比如下面的这个:
package cn.ganlixin.junit;
import org.junit.Test;
public class FirstExample {
/**
* 可以抛出异常
*/
@Test
public void testThrowException() throws InterruptedException {
Thread.sleep(1000);
}
}
1.4、约定俗成的规则
1、测试类的类名:要测试的类加上Test,比如有UserMapper类,如果要测试UserMapper类,那么对应的测试类名应该为UserMapperTest;
2、测试的方法名:test加上要测试的方法,比如测试UserMapper的addUser方法,那么测试方法名为testAddUser。
1.5、打印输出与断言
平时我们在写代码的时候,如果需要测试某个步骤中的某个变量值,那么通常的做法就是控制台打印这个变量即可。
同样的,在进行写单元测试的时候,我们也是可以使用控制台打印的,比如上面的第一个例子,输出hello world。
但是我们做单元测试,目标不是看他的输出结果,而是看运行结果是否和我们的预期一致!!
package cn.ganlixin.junit;
import org.junit.Test;
public class FirstExample {
@Test
public void testExample() {
int a = 10 / 3;
System.out.println(a); // 打印计算结果的值
System.out.println(a == 2); // 判断结果结果是否为2
// 等价于下面这种判断
if (a == 2) {
System.out.println(true);
} else {
System.out.println(false);
}
}
}
其实上面的运行结果是否和我们预期相符合,程序都会运行成功,且不会出现警告或者报错。
但其实我在计算10 / 3的时候,我的预期是2,但是如果不是2,应该证明单元测试未通过,此时应该有提示才对(而不是输出一个false来提示)。
这个就需要用到“断言”了,断言其实和if else判断是一样的,只不过语义上有点区别,上面使用断言来改写,就是下面这样:
package cn.ganlixin.junit;
import org.junit.Assert;
import org.junit.Test;
public class FirstExample {
@Test
public void testExample() {
int a = 10 / 3;
Assert.assertEquals(a, 2);
}
}
当我运行测试后,结果如下图所示,一下就能看出我的测试其实是未通过的,因为实际的结果和预期的结果是不同的。

一般来说,没有强制要求使用断言,那么就可以根据自己的喜好来选择是使用断言,还是控制台输出,我一般就是使用控制台输出,测试是否通过,完全靠自己人眼去分辨;况且,有时候我只是想要运行一段代码,对于结果并不关注,那么就不需要使用断言了。
二、单元测试的“AOP”
2.1、抛出问题
假设有下面这么一段代码,目的很简单,就是在两个测试方法的主要逻辑执行前,先执行before方法,然后在测试方法的主要逻辑执行完成后,再执行after方法。
package cn.ganlixin.junit;
import org.junit.Test;
public class JunitSecondTest {
@Test
public void testAOP111111() {
before();
System.out.println("real logic code 1111111111111");
after();
}
@Test
public void testAOP22222() {
before();
System.out.println("real logic code 22222222222");
after();
}
public void before() {
System.out.println("before");
}
public void after() {
System.out.println("after");
}
}
运行代码中的所有测试,输出结果如下:
before real logic code 1111111111111 after before real logic code 22222222222 after
其实这个就和Java里面广泛应用的AOP是一样的场景,junit也有几个注解来实现这个功能。
2.2、junit AOP
@Before,@After、@BeforeClass,@AfterClass这四个注解,依次介绍:
@Before:在每个测试方法执行前,先执行@Before注解的方法;
@After:在每个测试方法执行后,再执行@After注解的方法;
@BeforeClass:在整个单元测试执行过程中,最先执行,且只执行一次,一般用来加载资源(只需要加载一次,比如数据库连接,资源初始化);
@AfterClass:在所有单元测试执行完毕后,最后再执行@AfterClass注解的方法,只执行一次,一般用来释放资源。
先看下面使用示例:
package cn.ganlixin;
import org.junit.*;
public class JunitFlow {
@BeforeClass
public static void beforeClass(){
System.out.println("this is @BeforeClass");
}
@Before
public void before() {
System.out.println("this is @Before");
}
@Test
public void test11111() {
System.out.println("this is @Test test1111111");
}
@Test
public void test22222() {
System.out.println("this is @Test test222222");
}
@After
public void after() {
System.out.println("this is @After");
}
@AfterClass
public static void afterClass() {
System.out.println("this is @AfterClass");
}
}
运行上面的所有测试方法,结果如下:
this is @BeforeClass this is @Before this is @Test test1111111 this is @After this is @Before this is @Test test222222 this is @After this is @AfterClass
三、其他拓展
3.1、超时设置
如果我们对执行的测试代码有时间限制,比如要求测试的代码必须在1秒内执行完毕,那么如果1秒内没有执行完毕,就代表测试失败;反之,如果测试的代码在规定时间内执行完毕,那么就认为在时间方面通过了要求。
设置单元测试的执行时间限制(超时),只需要在@Test注解中,设置timeout属性即可,单位为毫秒:
package cn.ganlixin.junit;
import org.junit.Test;
/**
* 设置测试超时时间
*/
public class JunitTime {
@Test(timeout = 1000)
public void testNormal() throws InterruptedException {
Thread.sleep(500);
}
@Test(timeout = 1000)
public void testTimeOut() throws InterruptedException {
Thread.sleep(2000);
}
}
上面测试中
1、testNormal要求在1秒内执行完毕才认为是通过的,因为只休眠了500毫秒,所以肯定是没问题的;
2、testTimeOut要求在1秒内执行完,但是实际上需要2秒才能执行完,所以当运行1秒后,就不会继续运行,因为此时已经可以判定为测试未通过了。
运行上面的所有测试,结果如下:

另外,设置超时时间,可以防止程序无休止的运行,在设置的时间内未知性完毕,就立即终止。
3.2、忽略某个单元测试
一般情况下,我们一个测试类中,会有多个测试方法,这些测试方法可以一次性执行,也可以每次指定要执行某个方法,暂且不考虑这个问题;
忽略某个单元测试,是针对一次运行多个测试方法的那种情况,比如下面这样:
package cn.ganlixin.junit;
import org.junit.Ignore;
import org.junit.Test;
/**
* 忽略某个单元测试
*/
public class JunitIgnore {
@Ignore // 增加@Ignore注解,该测试中的代码不会真的执行,只会输出一行Ignored
@Test
public void testDoActionOne() {
System.out.println("action one");
}
@Test
public void testDoActionTwo() {
System.out.println("action two");
}
@Test
public void testDoActionThree() {
System.out.println("three");
}
}
一次性运行上面的所有测试,输出的结果如下:

3.3、预期异常
异常虽然是我们不希望看到的,但是我们可以制造一些测试数据,对于这些数据,程序运行时就应该抛出一些异常,这个时候我们才认为测试是通过的;否则,原本应该处理出现异常的,但是却没有没有发生异常,反而正常执行完了,证明我们的代码是存在问题,那么测试就是不通过的。
比如一个数组,只有2个元素,尝试访问第3个元素,就应该出现数组越界的异常;一个整数除以0,应该出现抛出算数异常....
package cn.ganlixin.junit;
import org.junit.Test;
import java.io.IOException;
/**
* 测试异常
*/
public class JunitException {
@Test(expected = IndexOutOfBoundsException.class)
public void testArray() {
int[] arr = {1, 2};
// 尝试获取第三个元素,会抛出下标越界异常
int b = arr[3];
}
@Test(expected = ArithmeticException.class)
public void testCompute() {
int a = 10 / 0; // 抛出算数异常
System.out.println();
}
@Test(expected = IOException.class)
public void testNormal() {
int a = 10;
// 执行正常,不会抛出异常,但是被认为是测试未通过,因为预期希望捕获到IOException
}
}
四、总结
一般来说,每开发一个功能模块,为其编写单元测试是一个比较推荐的做法,比如为DAO层的每一个操作DB的接口编写单元测试,需要注意的是:单元测试只对结果进行判断,而不能对处理逻辑正确性进行测试,所以当测试的结果和预期相同,只能说明结果正确,不代表处理流程没有错。