单元测试框架之Junit使用及原理分析

前言

单元测试用来保证我们的代码能够正常运行,输入一组数据,能够得到期望的结果,一般以方法作为最小单元。

简单使用

添加依赖

复制<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
</dependency>

简单例子

复制import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestJunit {
  @BeforeClass
  public static void beforeClass() {
    System.out.println("beforeClass");
  }
  @Before
  public void before() {
    System.out.println("before");
  }
  @Test
  public void testBase() {
    //如果实际值和期望值不一致,抛出AssertionError错误
    Assert.assertEquals(11, 10);
    Assert.assertThat(11, Matchers.equalTo(11));
  }
  @Test(expected = RuntimeException.class)
  public void testException() {
    throw new RuntimeException();
  }
  @Test(timeout = 3000) //单位毫秒
  public void testTimeout() {
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  @After
  public void after() {
    System.out.println("after");
  }
  @AfterClass
  public static void afterClass() {
    System.out.println("afterClass");
  }
}
  • @Test: 标记为一个测试用例,expected参数表示方法必须抛出指定的异常,timeout参数表示方法必须在指定时间内执行完
  • @BeforeClass: 必须为静态方法,在所有测试用例之前执行
  • @AfterClass: 必须为静态方法,在所有测试用例之后执行
  • @Before: 在每一个测试用例之前执行
  • @After: 在每一个测试用例之后执行
复制import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestJunitSource {
  public static void main(String[] args) {
    //执行测试类,得到一个结果,包含执行错误的用例信息
    Result result = JUnitCore.runClasses(TestJunit.class);
    for (Failure failure : result.getFailures()) {
      System.out.println(failure.toString());
    }
    //所有测试用例都是成功的
    if (result.wasSuccessful()) {
      System.out.println("Both Tests finished successfully...");
    }
  }
}

输出结果为

复制beforeClass
before
after
before
after
before
after
afterClass
testBase(com.imooc.sourcecode.java.test.junit.TestJunit): expected:<11> but was:<10>

符合每种注解的作用

原理分析

首先进入JUnitCore的runClasses()方法

复制public static Result runClasses(Class<?>... classes) {
        //defaultComputer()方法返回一个Computer对象,不重要
        return runClasses(defaultComputer(), classes);
    }

继续跟进去

复制public static Result runClasses(Computer computer, Class<?>... classes) {
        //创建JUnitCore对象并执行
        return new JUnitCore().run(computer, classes);
    }
public Result run(Computer computer, Class<?>... classes) {
        return run(Request.classes(computer, classes));
    }

核心分为两部分,根据测试类创建Runner对象,通过Runner对象执行测试方法。

创建Runner对象

复制public static Request classes(Computer computer, Class<?>... classes) {
        try {
            //这是一个RunnerBuilder(运行器构造器),用来创建Runner(运行器)
            AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
            //最终执行AllDefaultPossibilitiesBuilderel的runnerForClass()方法来获取Runner对象,并封装成Suite
            Runner suite = computer.getSuite(builder, classes);
            //将Runner包装成一个Request对象,核心还是Runner
            return runner(suite);
        }
    }

进入AllDefaultPossibilitiesBuilderel的runnerForClass()方法

复制@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
        //内部通过其他RunnerBuilder来创建Runner
        List<RunnerBuilder> builders = Arrays.asList(
                ignoredBuilder(),  //包含@Ignore注解的情况
                annotatedBuilder(), //包含@RunWith注解的情况
                suiteMethodBuilder(),//包含suite()方法
                junit3Builder(),//继承TestCase类
                junit4Builder());//默认BlockJUnit4ClassRunner

        for (RunnerBuilder each : builders) {
            //如果满足以上的任意一种情况,直接返回
            Runner runner = each.safeRunnerForClass(testClass);
            if (runner != null) {
                return runner;
            }
        }
        return null;
    }

通过@RunWith注解,我们可以扩展使用其他Runner实现类,如SpringRunner,配合Spring使用,可以帮我们创建IOC容器并注入需要的依赖。
我们的例子中以上情况都不满足,所以最终使用BlockJUnit4ClassRunner。

通过Runner对象执行测试方法

复制public Result run(Computer computer, Class<?>... classes) {
        //执行创建的Request对象,其中包含一个Runner对象
        return run(Request.classes(computer, classes));
    }

跟进去

复制public Result run(Request request) {
        //还是通过Runner对象,这里就是Suite类型,其中包含真正工作的运行器BlockJUnit4ClassRunner对象
        return run(request.getRunner());
    }

执行Runner对象

复制public Result run(Runner runner) {
        Result result = new Result();
        //创建一个运行监听器
        RunListener listener = result.createListener();
        notifier.addFirstListener(listener);
        try {
            //运行开始,这里就是记录开始时间
            notifier.fireTestRunStarted(runner.getDescription());
            //真正开始运行的地方
            runner.run(notifier);
            //运行结束,这里就是记录结束时间
            notifier.fireTestRunFinished(result);
        } finally {
            removeListener(listener);
        }
        return result;
    }

进入Suite父类ParentRunner的run()方法

复制@Override
public void run(final RunNotifier notifier) {
        EachTestNotifier testNotifier = new EachTestNotifier(notifier,
                getDescription());
        try {
            //创建Statement对象
            Statement statement = classBlock(notifier);
            //执行Statement
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            testNotifier.addFailedAssumption(e);
        } catch (StoppedByUserException e) {
            throw e;
        } catch (Throwable e) {
            testNotifier.addFailure(e);
        }
    }

看一下是如何创建Statement对象的,当前的Runner类型为Suite,其中的测试类为空,真正的测试类在BlockJUnit4ClassRunner中

复制protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);
        if (!areAllChildrenIgnored()) {
            //如果测试类包含@BeforeClass的方法,使用装饰者模式创建一个装饰器(RunBefores类型),在测试方法之前执行
            statement = withBeforeClasses(statement);
            //如果测试类包含@AfterClass的方法,使用装饰者模式创建一个装饰器(RunAfters类型),在测试方法之后执行
            statement = withAfterClasses(statement);
            //处理包含@ClassRule的方法,使用RunRules包装,暂时不管
            statement = withClassRules(statement);
        }
        return statement;
    }
//创建Statement对象
protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                //执行Statement的核心
                runChildren(notifier);
            }
        };
    }
private void runChildren(final RunNotifier notifier) {
        final RunnerScheduler currentScheduler = scheduler;
        try {
            //Suite对象的children只有BlockJUnit4ClassRunner对象
            for (final T each : getFilteredChildren()) {
                currentScheduler.schedule(new Runnable() {
                    public void run() {
                        //又跳转到了runChild()方法,这里的each就是BlockJUnit4ClassRunner对象
                        ParentRunner.this.runChild(each, notifier);
                    }
                });
            }
        } finally {
            currentScheduler.finished();
        }
    }

Suite的runChild()方法又会调用BlockJUnit4ClassRunner的run()方法,最终会执行BlockJUnit4ClassRunner的runChild()方法

复制@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        //获取方法的描述信息,不重要
        Description description = describeChild(method);
        //方法上是否包含@Ignore注解,表示忽略此方法
        if (isIgnored(method)) {
            notifier.fireTestIgnored(description);
        } else {
            runLeaf(methodBlock(method), description, notifier);
        }
    }
//创建一个Statement对象,并进行各种装饰
protected Statement methodBlock(FrameworkMethod method) {
        Object test;
        try {
            //创建测试类实例,这里就是TestJunit对象
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    //通过反射创建
                    return createTest();
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }
        //创建一个Statement对象,实际类型为InvokeMethod,通过反射执行测试方法
        Statement statement = methodInvoker(method, test);
        //处理@Test注解的expected参数,通过ExpectException类包装
        statement = possiblyExpectingExceptions(method, test, statement);
        //处理@Test注解的timeout参数,通过FailOnTimeout类包装
        statement = withPotentialTimeout(method, test, statement);
        //处理@Before注解,通过RunBefores类包装
        statement = withBefores(method, test, statement);
        //处理@After注解,通过RunAfters类包装
        statement = withAfters(method, test, statement);
        //处理@Rule注解,通过RunRules类包装,暂时不管
        statement = withRules(method, test, statement);
        return statement;
    }
//真正执行Statement对象
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) {
            //如果我们的测试方法抛出未被捕获的异常,就当做一次失败,主要是代码中抛出的AssertionError错误
            eachNotifier.addFailure(e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }

总结

  1. 通过RunnerBuilder(运行器构造器)创建一个Runner(运行器),最终的Runner为Suite类型,内部包含多个实际用来工作的Runner,
    默认为BlockJUnit4ClassRunner类型,我们可以通过@RunWith注解自定义Runner实现。
  2. BlockJUnit4ClassRunner解析测试类创建TestClass对象,并解析出其中包含@BeforeClass,@AfterClass,@Before,@After,@Test等注解的方法。
  3. BlockJUnit4ClassRunner对象创建一个Statement对象,使用RunBefores和RunAfters装饰此对象并执行(就是执行所有的测试方法)。
  4. 执行每一个测试方法,创建一个Statement对象,使用ExpectException,FailOnTimeout,RunBefores,RunAfters等类装饰此对象并执行。

参考

Java JUnit 单元测试小结
Junit源码阅读笔记一(从JunitCore开始)
Junit源码阅读笔记二(Runner构建)
Junit源码阅读笔记三(TestClass)
JUnit 5 教程 之 基础篇

posted @   strongmore  阅读(853)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示