JUnit
JJUnit是用于编写和运行可重复的自动化测试的开源测试框架, 这样可以保证我们的代码按预期工作。 JUnit可广泛用于工业和作为支架(从命令行)或IDE(如Eclipse)内单独的Java程序。
基础知识
JUnit的安装和使用都非常的简单。这里使用IDEA+Maven演示。
创建项目
使用Idea和Maven创建一个最简单的Java项目:

添加JUnit4.x依赖
1 <dependencies> 2 <dependency> 3 <groupId>junit</groupId> 4 <artifactId>junit</artifactId> 5 <version>4.11</version> 6 </dependency> 7 </dependencies> 8
这样子,就算完成了JUnit的基本安装。
注意:TestCase需要在src/test/java下编写。
TestCase
1package app; 2 3import org.junit.*; 4 5import java.util.ArrayList; 6import java.util.concurrent.atomic.AtomicInteger; 7 8/** 9 * 命名规则为:ClassNameTest 10 */ 11public class AppTest { 12 static final AtomicInteger count = new AtomicInteger(0); 13 private ArrayList testList; 14 15 /** 16 * 每次运行@Test方法,都会实例化一个对象。 17 */ 18 public AppTest() { 19 System.out.println(String.format("CONSTRUCT CALL %d", count.incrementAndGet())); 20 } 21 22 /** 23 * 指定一个静态方法,在所有@Test方法之前,执行一次。 24 */ 25 @BeforeClass 26 public static void onceExecutedBeforeAll() { 27 System.out.println("@BeforeClass: onceExecutedBeforeAll"); 28 } 29 30 /** 31 * 指定一个静态方法,在所有@Test方法之后,执行一次。 32 */ 33 @AfterClass 34 public static void onceExecutedAfterAll() { 35 System.out.println("@AfterClass: onceExecutedAfterAll"); 36 } 37 38 /** 39 * 在所有@Test方法之前执行 40 */ 41 @Before 42 public void executedBeforeEach() { 43 testList = new ArrayList(); 44 System.out.println("@Before: executedBeforeEach"); 45 } 46 47 /** 48 * 在所有@Test方法之后执行 49 */ 50 @After 51 public void executedAfterEach() { 52 testList.clear(); 53 System.out.println("@After: executedAfterEach"); 54 } 55 56 /** 57 * 命名规则:FunctionNameTest 58 */ 59 @Test 60 public void EmptyCollectionTest() { 61 Assert.assertTrue(testList.isEmpty()); 62 System.out.println("@Test: EmptyArrayList"); 63 64 } 65 66 /** 67 * 命名规则:FunctionNameTest 68 */ 69 @Test 70 public void OneItemCollectionTest() { 71 testList.add("oneItem"); 72 Assert.assertEquals(1, testList.size()); 73 System.out.println("@Test: OneItemArrayList"); 74 } 75 76 /** 77 * 忽略这个测试方法 78 */ 79 @Ignore 80 public void executionIgnoredTest() { 81 System.out.println("@Ignore: This execution is ignored"); 82 } 83} 84
上述是一个非常经典的例子,囊括了JUnit测试对象的生命周期。
运行TestCase
运行TestCase是非常方便的。现在几乎所有的主流IDE(Idea,Eclipse)都支持JUnit。以下是Idea的启动过程:

这样子就开启了调试模式运行TestCase。
运行日志
1@BeforeClass: onceExecutedBeforeAll 2CONSTRUCT CALL 1 3@Before: executedBeforeEach 4@Test: EmptyArrayList 5@After: executedAfterEach 6CONSTRUCT CALL 2 7@Before: executedBeforeEach 8@Test: OneItemArrayList 9@After: executedAfterEach 10@AfterClass: onceExecutedAfterAll
可以发现,JUnit的生命周期和注释保持一致。
扩展知识
@RunWith
使用JUnit的时候,有时候,需要自定义启动器(Runner)。这时候,我们可以通过@RunWith注解,来指定当前TestCase的Runner。 我们经常使用如下的Runner:
- Suite : 测试套件
- Parameterized : 参数化测试
- SpringJUnit4ClassRunner : Spring针对JUnit4.x的测试框架
JUnitCore
在没有IDE的情况下,我们可以借助main函数,来运行我们的TestCase:
1package runner; 2 3import app.AppTest; 4import org.junit.runner.JUnitCore; 5import org.junit.runner.Result; 6import org.junit.runner.notification.Failure; 7 8public class Main { 9 public static void main(String[] args) { 10 //通过JUnitCore指定,需要进行测试的TestCase 11 Result result = JUnitCore.runClasses(AppTest.class); 12 //搜集失败的测试用例信息 13 for (Failure fail : result.getFailures()) { 14 System.out.println(fail.toString()); 15 } 16 //判断,单元测试是否全部通过 17 if (result.wasSuccessful()) { 18 System.out.println("All tests finished successfully..."); 19 } 20 } 21} 22
这样子,我们就可以通过命令行运行JUnit。
Suite
在JUnit中,我们可以将几个TestCase合并在一起进行单元测试:

通过@Suite.SuiteClasses()将几个TestCase合并在一起,方便单元测试。
异常和超时
在某些情况下,我们需要测试异常和超时这两种情况。而这是通过@Test.expected和@Test.timeout来实现的。
1 /** 2 * expected 期待获取的异常类型 3 * timeout 测试用例超时时间 4 * */ 5 @Test(expected = Exception.class, timeout = 1000) 6 public void OneItemCollectionTest() throws Exception { 7 Thread.sleep(500); 8 System.out.println("@Test: OneItemArrayList"); 9 } 10
Spring 整合
依赖
Spring提供了spring-test来支持JUnit的测试框架。引入依赖:
1 <dependency> 2 <groupId>org.springframework</groupId> 3 <artifactId>spring-test</artifactId> 4 <version>${spring-version}</version> 5 </dependency> 6
SpringTest
为了避免每个TestCase都添加@RunWith等注解,这里引入SpringTest方便TestCase编写:
1//http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#testing 2 3//Spring针对JUnit4.x的支持Runner 4@RunWith(SpringJUnit4ClassRunner.class) 5//Spring配置类 6@ContextConfiguration(classes = {SpringConf.class}) 7//支持Spring MVC 8@WebAppConfiguration 9//默认回滚 10@Rollback 11//默认事务 12@Transactional 13public abstract class SpringTest { 14 15 //Spring 上下文 16 @Autowired 17 private WebApplicationContext wac; 18 //Spring MVC测试支持类 19 private MockMvc mockMvc; 20 21 22 @Before 23 public void init() { 24 //构造mockMvc 25 //不知道为什么Spring小组,不提供MockMvc注解方式@Autowired方式初始化 26 mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); 27 } 28 29 //获取Spring MVC测试支持对象MockMvc 30 public MockMvc getMockMvc() { 31 return mockMvc; 32 } 33} 34
这样子,就定义了一个测试基类。具体的TestCase只需要继承这个测试基类即可。
Spring MVC测试
1//继承测试基类 2public class ArticleCtrlTest extends SpringTest { 3 //路径 4 final static String PATH = "/main/ArticleCtrl/"; 5 6 //支持@Autowired方式 7 @Autowired 8 ArticleIo articleIo; 9 10 11 @Test 12 public void getTest() throws Exception { 13 final Article article = new Article(null, "测试数据", false); 14 //插入一条数据 15 articleIo.insert(article); 16 //检测接口 17 getMockMvc().perform(MockMvcRequestBuilders.post(PATH + "get").param("id", article.getId())).andDo(new ResultHandler() { 18 @Override 19 public void handle(MvcResult result) throws Exception { 20 JSONObject ret = JSON.parseObject(result.getResponse().getContentAsString()); 21 //ok 22 Assert.assertTrue(ret.getInteger("code") == 0); 23 //check 24 Assert.assertTrue(ret.getJSONObject("msg").getString("id").equals(article.getId())); 25 } 26 }); 27 } 28 29} 30
以上,就是一个简单的Spring MVC测试用例。对于Dao或者Service测试就更加简单了。
注意:getTest的事务会进行回滚操作,不会真正的写入数据库。
运行截图

项目地址:java-fast-framework
执行流程
JUnit的测试流程大致如下:
- 指定需要测试的
TestCase。假如采用Maven构建,则默认为所有/src/test/java/**Test类。 - JUnit加载
TestCase的@RunWith指向的Runner。默认为:BlockJUnit4ClassRunner。 - JUnit实例化
Runner,然后调用Runner#run(RunNotifier notifier)方法,测试指定的TestCase。注意:Runner需要拥有一个Runner(Class clz)类型的构造函数。 Runner通过notifier记录方法执行结果。- JUnit收集所有
TestCase的执行结果,然后打印报告。
注意:JUnit读取TestCase注解(@RunWith,@Test...)的时候,会遍历TestCase整个继承链。
BlockJUnit4ClassRunner
我们以BlockJUnit4ClassRunner这个Runner分析具体Runner#run的过程:
1ParentRunner: 2 3 //对指定的TestCase进行检测 4 @Override 5 public void run(final RunNotifier notifier) { 6 EachTestNotifier testNotifier = new EachTestNotifier(notifier, 7 getDescription()); 8 try { 9 //创造一个执行Block 10 Statement statement = classBlock(notifier); 11 //执行具体的Block 12 statement.evaluate(); 13 } catch (AssumptionViolatedException e) { 14 testNotifier.addFailedAssumption(e); 15 } catch (StoppedByUserException e) { 16 throw e; 17 } catch (Throwable e) { 18 testNotifier.addFailure(e); 19 } 20 } 21 22ParentRunner: 23 24 //创建执行Block 25 protected Statement classBlock(final RunNotifier notifier) { 26 //获取待执行的语句,BlockJUnit4ClassRunner 为执行所有@Test方法语句 27 Statement statement = childrenInvoker(notifier); 28 if (!areAllChildrenIgnored()) { 29 //处理@BeforeClass 30 statement = withBeforeClasses(statement); 31 //处理@AfterClass 32 statement = withAfterClasses(statement); 33 //处理@ClassRule 34 statement = withClassRules(statement); 35 } 36 return statement; 37 } 38 39ParentRunner: 40 //构造一个通过Statement,这个Statement具体执行的时候,会调用runChildren方法。 41 protected Statement childrenInvoker(final RunNotifier notifier) { 42 return new Statement() { 43 @Override 44 public void evaluate() { 45 //语句被调用执行的时候,会真正的执行函数 46 runChildren(notifier); 47 } 48 }; 49 }
这样子,就完成了Statement的构造过程。然后我们再看一下刚刚创建出来的Statement#evaluate函数:
1ParentRunner: 2 //构造一个通过Statement,这个Statement具体执行的时候,会调用runChildren方法。 3 protected Statement childrenInvoker(final RunNotifier notifier) { 4 return new Statement() { 5 @Override 6 public void evaluate() { 7 //语句被调用执行的时候,会真正的执行函数 8 runChildren(notifier); 9 } 10 }; 11 } 12 13ParentRunner: 14 //具体执行的过程 15 private void runChildren(final RunNotifier notifier) { 16 //获取当前的调度器,默认为主线程测试 17 final RunnerScheduler currentScheduler = scheduler; 18 try { 19 //获取要测试的对象 20 for (final T each : getFilteredChildren()) { 21 currentScheduler.schedule(new Runnable() { 22 public void run() { 23 //进行刚刚给定的对象 24 ParentRunner.this.runChild(each, notifier); 25 } 26 }); 27 } 28 } finally { 29 currentScheduler.finished(); 30 } 31 } 32
上述的执行过程中,涉及到两个点:
- getFilteredChildren:获取待测试的对象集合
- runChild:进行具体的测试
我们,先看看getFilteredChildren方法:
1ParentRunner: 2 //获取要执行的对象集合 3 private Collection<T> getFilteredChildren() { 4 if (filteredChildren == null) { 5 synchronized (childrenLock) { 6 if (filteredChildren == null) { 7 //通过getChildren方法,委托子类,然后获取具体要测试的对象信息 8 filteredChildren = Collections.unmodifiableCollection(getChildren()); 9 } 10 } 11 } 12 return filteredChildren; 13 } 14 15BlockJUnit4ClassRunner: 16 17 //父类ParentRunner#getChildren具体实现方法,用来搜集执行对象信息 18 @Override 19 protected List<FrameworkMethod> getChildren() { 20 return computeTestMethods(); 21 } 22 23BlockJUnit4ClassRunner: 24 25 //搜索@Test方法信息 26 protected List<FrameworkMethod> computeTestMethods() { 27 //搜索@Test方法信息,包括所有的父类 28 return getTestClass().getAnnotatedMethods(Test.class); 29 } 30
这样子,就搜集了待测试的@Test方法对象集合。然后,我们在看看具体的测试runChild:
1BlockJUnit4ClassRunner: 2 3 @Override 4 protected void runChild(final FrameworkMethod method, RunNotifier notifier) { 5 Description description = describeChild(method); 6 //判断这个对象是否@Ignored 7 if (isIgnored(method)) { 8 notifier.fireTestIgnored(description); 9 } else { 10 //1. 根据这个对象,通过methodBlock创建执行Block 11 //2. 执行这个Block 12 runLeaf(methodBlock(method), description, notifier); 13 } 14 } 15 16BlockJUnit4ClassRunner: 17 18 //创建待执行的Block 19 protected Statement methodBlock(FrameworkMethod method) { 20 Object test; 21 try { 22 //构造一个新的对象!! 23 //也就是说,一个@Test方法对应一个对象 24 test = new ReflectiveCallable() { 25 @Override 26 protected Object runReflectiveCall() throws Throwable { 27 return createTest(); 28 } 29 }.run(); 30 } catch (Throwable e) { 31 return new Fail(e); 32 } 33 34 Statement statement = methodInvoker(method, test); 35 //处理@Test#expected 36 statement = possiblyExpectingExceptions(method, test, statement); 37 //处理@Test#timeout 38 statement = withPotentialTimeout(method, test, statement); 39 //处理@Before 40 statement = withBefores(method, test, statement); 41 //处理@After 42 statement = withAfters(method, test, statement); 43 //处理@Rule 44 statement = withRules(method, test, statement); 45 return statement; 46 } 47 48BlockJUnit4ClassRunner: 49 50 //执行刚刚创建的Block 51 protected final void runLeaf(Statement statement, Description description, 52 RunNotifier notifier) { 53 EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description); 54 eachNotifier.fireTestStarted(); 55 try { 56 //执行 57 statement.evaluate(); 58 } catch (AssumptionViolatedException e) { 59 eachNotifier.addFailedAssumption(e); 60 } catch (Throwable e) { 61 eachNotifier.addFailure(e); 62 } finally { 63 eachNotifier.fireTestFinished(); 64 } 65 }
注意:BlockJUnit4ClassRunner#methodBlock可以发现,每测试一个@Test方法,都会创建一个对象。
到此,BlockJUnit4ClassRunner#Runner#run(RunNotifier notifier)的运行流程,就基本分析完毕了。
SpringJUnit4ClassRunner
Spring 通过SpringJUnit4ClassRunner来支持JUnit。通过SpringJUnit4ClassRunner,我们可以实现如下特性:
- ApplicationContext仅仅初始化一次。
- SpringMVC 支持
- @Autowired 支持
- @Transactional和@Rollback支持
SpringJUnit4ClassRunner继承于BlockJUnit4ClassRunner对象,通过重写构造函数和createTest来实现了以上的特性:
- 构造函数:ApplicationContext仅仅初始化一次。
- createTest:@Autowired IOC支持 和 @Transactional和@Rollback 等AOP支持。
最佳实践
这里总结一下JUnit最佳实践:
- 一个类,一个测试类;一个函数,一个测试函数;
- 命名规则: ClassNameTest 和 FunctionNameTest。
- 切勿@Test函数相互调用。
- 合理使用测试基类(如:SpringTest)。
- 覆盖率:业务类型>=60%,工具类型>=80%。