JUnit学习笔记

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的测试流程大致如下:

  1. 指定需要测试的TestCase。假如采用Maven构建,则默认为所有/src/test/java/**Test类。
  2. JUnit加载TestCase的@RunWith指向的Runner。默认为:BlockJUnit4ClassRunner
  3. JUnit实例化Runner,然后调用Runner#run(RunNotifier notifier)方法,测试指定的TestCase。注意:Runner需要拥有一个Runner(Class clz)类型的构造函数。
  4. Runner通过notifier记录方法执行结果。
  5. 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,我们可以实现如下特性:

  1. ApplicationContext仅仅初始化一次。
  2. SpringMVC 支持
  3. @Autowired 支持
  4. @Transactional和@Rollback支持

SpringJUnit4ClassRunner继承于BlockJUnit4ClassRunner对象,通过重写构造函数createTest来实现了以上的特性:

  1. 构造函数:ApplicationContext仅仅初始化一次。
  2. createTest:@Autowired IOC支持 和 @Transactional和@Rollback 等AOP支持。

最佳实践

这里总结一下JUnit最佳实践:

  1. 一个类,一个测试类;一个函数,一个测试函数;
  2. 命名规则: ClassNameTest 和 FunctionNameTest。
  3. 切勿@Test函数相互调用。
  4. 合理使用测试基类(如:SpringTest)。
  5. 覆盖率:业务类型>=60%,工具类型>=80%。

参考

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java基础知识随身记

2018年11月12日20:51:35一、基础知识:1、JVM、JRE和JDK的区别:JVM(JavaVirtualMachine):java虚拟机,用于保证java的跨平台的特性。  java语言是跨平台,jvm不是跨平台的。JRE(JavaRuntimeEnvironment):java的运行环境,包括jvmjava的核心类

java单元测试工具

前言随着DevOp的不断流行,自动化测试慢慢成为Java开发者的关注点。因此,本文将分享10款优秀的单元测试框架和库,它们可以帮助Java开发人员在其Java项目上编写单元测试和集成测试。1\.JUnit我绝对JUnit不需要太多的介绍了。即使您是Java初学者,也可能听说过它。它可以帮助您为Java代码编写单元测试。几

java之Junit

Java之Junit的使用法则第一,Junit是指单元测试,用来对java程序的测试。以后可用来对代码的测试第二,Junit使用方法命名规范:publicvoid方法名(){}第三,具体实现步奏1.创建java类 !(https://oscimg.oschina.net/oscnet/d4a6428c8c4116