JUnit学习笔记

Stella981
• 阅读 769

JUnit

JJUnit是用于编写和运行可重复的自动化测试的开源测试框架, 这样可以保证我们的代码按预期工作。 JUnit可广泛用于工业和作为支架(从命令行)或IDE(如Eclipse)内单独的Java程序。

基础知识

JUnit的安装和使用都非常的简单。这里使用IDEA+Maven演示。

创建项目

使用Idea和Maven创建一个最简单的Java项目:

JUnit学习笔记

添加JUnit4.x依赖

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
    </dependencies>
    

这样子,就算完成了JUnit的基本安装。

注意:**TestCase需要在src/test/java下编写。**

TestCase

package app;

import org.junit.*;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 命名规则为:ClassNameTest
 */
public class AppTest {
    static final AtomicInteger count = new AtomicInteger(0);
    private ArrayList testList;

    /**
     * 每次运行@Test方法,都会实例化一个对象。
     */
    public AppTest() {
        System.out.println(String.format("CONSTRUCT CALL %d", count.incrementAndGet()));
    }

    /**
     * 指定一个静态方法,在所有@Test方法之前,执行一次。
     */
    @BeforeClass
    public static void onceExecutedBeforeAll() {
        System.out.println("@BeforeClass: onceExecutedBeforeAll");
    }

    /**
     * 指定一个静态方法,在所有@Test方法之后,执行一次。
     */
    @AfterClass
    public static void onceExecutedAfterAll() {
        System.out.println("@AfterClass: onceExecutedAfterAll");
    }

    /**
     * 在所有@Test方法之前执行
     */
    @Before
    public void executedBeforeEach() {
        testList = new ArrayList();
        System.out.println("@Before: executedBeforeEach");
    }

    /**
     * 在所有@Test方法之后执行
     */
    @After
    public void executedAfterEach() {
        testList.clear();
        System.out.println("@After: executedAfterEach");
    }

    /**
     * 命名规则:FunctionNameTest
     */
    @Test
    public void EmptyCollectionTest() {
        Assert.assertTrue(testList.isEmpty());
        System.out.println("@Test: EmptyArrayList");

    }

    /**
     * 命名规则:FunctionNameTest
     */
    @Test
    public void OneItemCollectionTest() {
        testList.add("oneItem");
        Assert.assertEquals(1, testList.size());
        System.out.println("@Test: OneItemArrayList");
    }

    /**
     * 忽略这个测试方法
     */
    @Ignore
    public void executionIgnoredTest() {
        System.out.println("@Ignore: This execution is ignored");
    }
}

上述是一个非常经典的例子,囊括了JUnit测试对象的生命周期

运行TestCase

运行TestCase是非常方便的。现在几乎所有的主流IDE(Idea,Eclipse)都支持JUnit。以下是Idea的启动过程:

JUnit学习笔记

这样子就开启了调试模式运行TestCase

运行日志

@BeforeClass: onceExecutedBeforeAll
CONSTRUCT CALL 1
@Before: executedBeforeEach
@Test: EmptyArrayList
@After: executedAfterEach
CONSTRUCT CALL 2
@Before: executedBeforeEach
@Test: OneItemArrayList
@After: executedAfterEach
@AfterClass: onceExecutedAfterAll

可以发现,JUnit的生命周期和注释保持一致。

扩展知识

@RunWith

使用JUnit的时候,有时候,需要自定义启动器(Runner)。这时候,我们可以通过@RunWith注解,来指定当前TestCase的Runner。 我们经常使用如下的Runner:

  • Suite : 测试套件
  • Parameterized : 参数化测试
  • SpringJUnit4ClassRunner : Spring针对JUnit4.x的测试框架

JUnitCore

在没有IDE的情况下,我们可以借助main函数,来运行我们的TestCase

package runner;

import app.AppTest;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class Main {
    public static void main(String[] args) {
        //通过JUnitCore指定,需要进行测试的TestCase
        Result result = JUnitCore.runClasses(AppTest.class);
        //搜集失败的测试用例信息
        for (Failure fail : result.getFailures()) {
            System.out.println(fail.toString());
        }
        //判断,单元测试是否全部通过
        if (result.wasSuccessful()) {
            System.out.println("All tests finished successfully...");
        }
    }
}

这样子,我们就可以通过命令行运行JUnit。

Suite

在JUnit中,我们可以将几个TestCase合并在一起进行单元测试:

JUnit学习笔记

通过@Suite.SuiteClasses()将几个TestCase合并在一起,方便单元测试。

异常和超时

在某些情况下,我们需要测试异常超时这两种情况。而这是通过@Test.expected@Test.timeout来实现的。

    /**
     * expected 期待获取的异常类型
     * timeout 测试用例超时时间
     * */
    @Test(expected = Exception.class, timeout = 1000)
    public void OneItemCollectionTest() throws Exception {
        Thread.sleep(500);
        System.out.println("@Test: OneItemArrayList");
    }
    

Spring 整合

依赖

Spring提供了spring-test来支持JUnit的测试框架。引入依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring-version}</version>
    </dependency>

SpringTest

**为了避免每个TestCase都添加@RunWith等注解,这里引入SpringTest方便TestCase编写**:

//http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#testing

//Spring针对JUnit4.x的支持Runner
@RunWith(SpringJUnit4ClassRunner.class)
//Spring配置类
@ContextConfiguration(classes = {SpringConf.class})
//支持Spring MVC
@WebAppConfiguration
//默认回滚
@Rollback
//默认事务
@Transactional
public abstract class SpringTest {
    
    //Spring 上下文
    @Autowired
    private WebApplicationContext wac;
    //Spring MVC测试支持类
    private MockMvc mockMvc;


    @Before
    public void init() {
        //构造mockMvc
        //不知道为什么Spring小组,不提供MockMvc注解方式@Autowired方式初始化
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
    
    //获取Spring MVC测试支持对象MockMvc
    public MockMvc getMockMvc() {
        return mockMvc;
    }
}

这样子,就定义了一个测试基类。具体的TestCase只需要继承这个测试基类即可。

Spring MVC测试

//继承测试基类
public class ArticleCtrlTest extends SpringTest {
    //路径
    final static String PATH = "/main/ArticleCtrl/";
    
    //支持@Autowired方式
    @Autowired
    ArticleIo articleIo;
    
    
    @Test
    public void getTest() throws Exception {
        final Article article = new Article(null, "测试数据", false);
        //插入一条数据
        articleIo.insert(article);
        //检测接口
        getMockMvc().perform(MockMvcRequestBuilders.post(PATH + "get").param("id", article.getId())).andDo(new ResultHandler() {
            @Override
            public void handle(MvcResult result) throws Exception {
                JSONObject ret = JSON.parseObject(result.getResponse().getContentAsString());
                //ok
                Assert.assertTrue(ret.getInteger("code") == 0);
                //check
                Assert.assertTrue(ret.getJSONObject("msg").getString("id").equals(article.getId()));
            }
        });
    }
    
}

以上,就是一个简单的Spring MVC测试用例。对于Dao或者Service测试就更加简单了。

注意:getTest的事务会进行回滚操作,不会真正的写入数据库。

运行截图

JUnit学习笔记

项目地址: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的过程:

ParentRunner:
    
    //对指定的TestCase进行检测
    @Override
    public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        try {
            //创造一个执行Block
            Statement statement = classBlock(notifier);
            //执行具体的Block
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            testNotifier.addFailedAssumption(e);
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            testNotifier.addFailure(e);
        }
    }
    
ParentRunner:
    
    //创建执行Block
    protected Statement classBlock(final RunNotifier notifier) {
        //获取待执行的语句,BlockJUnit4ClassRunner 为执行所有@Test方法语句
        Statement statement = childrenInvoker(notifier);
        if (!areAllChildrenIgnored()) {
            //处理@BeforeClass
            statement = withBeforeClasses(statement);
            //处理@AfterClass
            statement = withAfterClasses(statement);
            //处理@ClassRule
            statement = withClassRules(statement);
        }
        return statement;
    }
    
ParentRunner:
    //构造一个通过Statement,这个Statement具体执行的时候,会调用runChildren方法。
    protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                //语句被调用执行的时候,会真正的执行函数
                runChildren(notifier);
            }
        };
    }

这样子,就完成了Statement的构造过程。然后我们再看一下刚刚创建出来的Statement#evaluate函数:

ParentRunner:
    //构造一个通过Statement,这个Statement具体执行的时候,会调用runChildren方法。
    protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                //语句被调用执行的时候,会真正的执行函数
                runChildren(notifier);
            }
        };
    }

ParentRunner:
    //具体执行的过程
    private void runChildren(final RunNotifier notifier) {
        //获取当前的调度器,默认为主线程测试
        final RunnerScheduler currentScheduler = scheduler;
        try {
            //获取要测试的对象
            for (final T each : getFilteredChildren()) {
                currentScheduler.schedule(new Runnable() {
                    public void run() {
                        //进行刚刚给定的对象
                        ParentRunner.this.runChild(each, notifier);
                    }
                });
            }
        } finally {
            currentScheduler.finished();
        }
    }
    

上述的执行过程中,涉及到两个点:

  • getFilteredChildren:获取待测试的对象集合
  • runChild:进行具体的测试

我们,先看看getFilteredChildren方法:

ParentRunner:
    //获取要执行的对象集合
    private Collection<T> getFilteredChildren() {
        if (filteredChildren == null) {
            synchronized (childrenLock) {
                if (filteredChildren == null) {
                    //通过getChildren方法,委托子类,然后获取具体要测试的对象信息
                    filteredChildren = Collections.unmodifiableCollection(getChildren());
                }
            }
        }
        return filteredChildren;
    }
    
BlockJUnit4ClassRunner:
    
    //父类ParentRunner#getChildren具体实现方法,用来搜集执行对象信息
    @Override
    protected List<FrameworkMethod> getChildren() {
        return computeTestMethods();
    }
    
BlockJUnit4ClassRunner:

    //搜索@Test方法信息
    protected List<FrameworkMethod> computeTestMethods() {
        //搜索@Test方法信息,包括所有的父类
        return getTestClass().getAnnotatedMethods(Test.class);
    }

这样子,就搜集了待测试的@Test方法对象集合。然后,我们在看看具体的测试runChild

BlockJUnit4ClassRunner:

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        //判断这个对象是否@Ignored
        if (isIgnored(method)) {
            notifier.fireTestIgnored(description);
        } else {
            //1. 根据这个对象,通过methodBlock创建执行Block
            //2. 执行这个Block
            runLeaf(methodBlock(method), description, notifier);
        }
    }
    
BlockJUnit4ClassRunner:

    //创建待执行的Block
    protected Statement methodBlock(FrameworkMethod method) {
        Object test;
        try {
            //构造一个新的对象!!
            //也就是说,一个@Test方法对应一个对象
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest();
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }

        Statement statement = methodInvoker(method, test);
        //处理@Test#expected
        statement = possiblyExpectingExceptions(method, test, statement);
        //处理@Test#timeout
        statement = withPotentialTimeout(method, test, statement);
        //处理@Before
        statement = withBefores(method, test, statement);
        //处理@After
        statement = withAfters(method, test, statement);
        //处理@Rule
        statement = withRules(method, test, statement);
        return statement;
    }
    
BlockJUnit4ClassRunner:

    //执行刚刚创建的Block
    protected final void runLeaf(Statement statement, Description description,
            RunNotifier notifier) {
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
        eachNotifier.fireTestStarted();
        try {
            //执行
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            eachNotifier.addFailure(e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }

注意: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%。

参考

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
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
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java基础知识随身记
2018年11月12日20:51:35一、基础知识:1、JVM、JRE和JDK的区别:JVM(JavaVirtualMachine):java虚拟机,用于保证java的跨平台的特性。  java语言是跨平台,jvm不是跨平台的。JRE(JavaRuntimeEnvironment):java的运行环境,包括jvmjava的核心类
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
java单元测试工具
前言随着DevOp的不断流行,自动化测试慢慢成为Java开发者的关注点。因此,本文将分享10款优秀的单元测试框架和库,它们可以帮助Java开发人员在其Java项目上编写单元测试和集成测试。1\.JUnit我绝对JUnit不需要太多的介绍了。即使您是Java初学者,也可能听说过它。它可以帮助您为Java代码编写单元测试。几
Wesley13 Wesley13
3年前
java之Junit
Java之Junit的使用法则第一,Junit是指单元测试,用来对java程序的测试。以后可用来对代码的测试第二,Junit使用方法命名规范:publicvoid方法名(){}第三,具体实现步奏1.创建java类 !(https://oscimg.oschina.net/oscnet/d4a6428c8c4116
Souleigh ✨ Souleigh ✨
3年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Wesley13 Wesley13
3年前
Python单元测试框架unittest简介
Python单元测试框架,其中的有些部分参照PyUnit。PyUnit是JUnit的python语言版本,它是由KentBeck和ErichGamma创建。JUnit是Kent的Smalltalk测试框架的Java版本。JUnit的每部分都是标准的单元测试框架,很多语言都受他启发而开发自身的单元测试框架。        unittest支持自动化测
Stella981 Stella981
3年前
Junit
junit4.x(1)、使用junit4.x版本进行单元测试时,不用测试类继承TestCase父类,因为,junit4.x全面引入了Annotation来执行我们编写的测试。\4\(2)、junit4.x版本,引用了注解的方式,进行单元测试;(3)、junit4.x版本我们常用的注解:A、@Before注解:与junit3.
Stella981 Stella981
3年前
Spring Boot(十二)单元测试JUnit
一、介绍JUnit是一款优秀的开源Java单元测试框架,也是目前使用率最高最流行的测试框架,开发工具Eclipse和IDEA对JUnit都有很好的支持,JUnit主要用于白盒测试和回归测试。<!more白盒测试:把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人员是公开的;回归测试