JUnit4源码初探

JUnit4源码初探

  Junit是广泛使用的Java单元测试类库,官网地址:junit4主页junit4开源地址(github)。网上有许多关于junit的使用教程,这里附上两个教程供大家参考:junit教程一junit教程二(英文)。Junit4使用注解实现测试执行,这篇文章主要粗略分析Junit4的实现原理,代码使用目前的最新版本junit-4.13-SNAPSHOT进行分析。

JUnit4框架使用

  Junit4使用非常方便,编写好测试类,在命令行中编译运行即可。

javac -cp .;junit-4.XX.jar XXX.java //编译
java -cp .:junit-4.XX.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore XXXTest //运行

  JUnitCore是Junit4的入口类,在调用主函数mian后,依次调用runMain->JUnitCommandLineParseResult.parse->Request.class->JUnitCore.run,其中Runner是junit框架的核心类,代表一个测试抽象。Ruquest相当于Runner工厂,通过调用Request.class方法生成特定的Runner。调用次序如下图:

main函数

  Junit4可嵌入代码中调用,如下:

   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(TestJunit.class);
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
      System.out.println(result.wasSuccessful());
   }

  JUnitCore.runClasses中同样调用Request.class生成特定Runner。其中,Result代表测试结果类。

Runner/RunnerBuilder

  Runner是junit框架中的核心类,由RunnerBuilder创建。junit4默认使用AllDefaultPossibilitiesBuilder生成不同的RunnerBuilder。runnerForClass方法是RunnerBuilder生成Runner的方法,AllDefaultPossibilitiesBuilder重写runnerForClass方法如下:

    public Runner runnerForClass(Class<?> testClass) throws Throwable {
        List<RunnerBuilder> builders = Arrays.asList(
                ignoredBuilder(),
                annotatedBuilder(),
                suiteMethodBuilder(),
                junit3Builder(),
                junit4Builder());

        for (RunnerBuilder each : builders) {
            Runner runner = each.safeRunnerForClass(testClass);
            if (runner != null) {
                return runner;
            }
        }
        return null;
    }

  可以看出,AllDefaultPossibilitiesBuilder建立builder列表并依照列表依次找到合适的RunnerBuilder,其查找顺序是:

builder顺序

  不同的RunnerBuilder生成不同的Runner。Junit4框架中,BlockJUnit4ClassRunner是主要的实现的Runner。BlockJUnit4ClassRunner继承ParentRunner,ParentRunner是实现形成父子关系的抽象测试类,其父子测试关系呈父子关系。在BlockJUnit4ClassRunner中,其子测试为FrameworkMethod,代表测试类的方法单元。

runner

  Suite是junit4中另一个值得关注的Runner,Suite继承ParentRunner,其子测试是其他Runner,代表一组测试的抽象类。在JUnitCore中,通过调用Request.class方法生成的Runner为一个Suite,其中包含其他Runner测试类。

Statement机制

  Statement机制是junit4中实现测试的重要一环,Runner利用Statment进行实际的测试运行。Statment的定义很简单,包含一个evaluate接口。

public abstract class Statement {
    public abstract void evaluate() throws Throwable;
}

  利用Statement容易实现对测试事件的拦截和扩展,类似AOP。例如在ParentRunner的classBlock方法实现block逻辑。

    protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);  //封装子测试
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);  //封装@BeforeClass测试,返回RunBefores
            statement = withAfterClasses(statement);  //封装@AfterClass测试,返回RunAfters
            statement = withClassRules(statement);  //封装@ClassRule测试,返回RunRules
        }
        return statement;
    }

    protected Statement withBeforeClasses(Statement statement) {
        List<FrameworkMethod> befores = testClass
                .getAnnotatedMethods(BeforeClass.class);
        return befores.isEmpty() ? statement :
                new RunBefores(statement, befores, null);  //生成RunBefores对象
    }

  RunBefores,RunAfters,RunRules都是Statement对象的代理类,其构造函数传入Statement引用,通过控制evaluate方法的调用实现对原有测试的控制,并实现对测试事件不同时期的拦截。其中,RunBefores的实现如下:

public class RunBefores extends Statement {
    private final Statement next;
    private final Object target;
    private final List<FrameworkMethod> befores;

    public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
        this.next = next;
        this.befores = befores;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        for (FrameworkMethod before : befores) {
            before.invokeExplosively(target);
        }
        next.evaluate();
    }
}

BlockJUnit4ClassRunner

  BlockJUnit4ClassRunner是JUnit4的默认实现Runner,父类ParentRunner实现了@BeforeClass,@AfterClass,@ClassRule逻辑,BlockJUnit4ClassRunner主要工作是实现runChild方法,并调用测试类方法,runChild实现如下:

protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
    Description description = describeChild(method);
    if (isIgnored(method)) {
        // @Ignore注解
        notifier.fireTestIgnored(description);
    } else {
        Statement statement;
        try {
            statement = methodBlock(method); //调用methodBlock
        }
        catch (Throwable ex) {
            statement = new Fail(ex);   // 封装error
        }
        // 运行statement
        runLeaf(statement, description, notifier);
    }
}

protected Statement methodBlock(final FrameworkMethod method) {
    Object test;
    try {
        // 获取测试类实例
        test = new ReflectiveCallable() {
            @Override
            protected Object runReflectiveCall() throws Throwable {
                return createTest(method);
            }
        }.run();
    } catch (Throwable e) {
        return new Fail(e); // 封装error
    }

    Statement statement = methodInvoker(method, test);  // 封装测试方法
    statement = possiblyExpectingExceptions(method, test, statement);
    statement = withPotentialTimeout(method, test, statement);
    statement = withBefores(method, test, statement);  // 封装@Before测试
    statement = withAfters(method, test, statement);  // 封装@After测试
    statement = withRules(method, test, statement);  // 封装@Rule测试
    return statement;
}

  runChild调用methodBlock方法对测试方法进行封装,然后调用runLeaf运行statement.evaluate()。

Assert/Assume

  Assert类提供了很多断言方法,如assertTrue,assertEquals,assertNull等,在JUnit框架比较常用,当判断失败时抛出AssertError。Assume与Assert相似,提供如assumeTrue等方法,判断失败时抛出AssumptionViolatedException,表示条件不满足是放弃当前测试。

  AssertError和AssumptionViolatedException的捕获在BlockJUnit4ClassRunner的runLeaf方法中进行,runLeaf实现如下:

protected final void runLeaf(Statement statement, Description description,
                             RunNotifier notifier) {
    EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
    eachNotifier.fireTestStarted();
    try {
        // 运行statement
        statement.evaluate();
    } catch (AssumptionViolatedException e) {
        eachNotifier.addFailedAssumption(e);  //捕获AssumptionViolatedException
    } catch (Throwable e) {
        eachNotifier.addFailure(e); //捕获AssertError和其他异常
    } finally {
        eachNotifier.fireTestFinished();
    }
}

@RunWith

  JUnit4框架可以使用@RunWith注解改变默认Runner,AllDefaultPossibilitiesBuilder在遇到@RunWith注解的测试类时使用AnnotatedBuilder创建Runner,AnnotatedBuilder重写runnerForClass方法如下:

// AnnotatedBuilder的runnerForClass方法
public Runner runnerForClass(Class<?> testClass) throws Exception {
    for (Class<?> currentTestClass = testClass; currentTestClass != null;
         currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
        RunWith annotation = currentTestClass.getAnnotation(RunWith.class);  // @RunWith注解
        if (annotation != null) {
            // 根据注解创建Runner
            return buildRunner(annotation.value(), testClass);
        }
    }
    return null;
}

  JUnit4框架提供许多有用的Runner,如Suite,Parameterized,Category等。

Junit之源码测试

  大部分java程序使用JUnit框架进行测试,而这放在JUnit代码上时会出现问题,JUnit源码使用什么来测试代码?在当前使用的junit-4.13-SNAPSHOT版本上,junit使用自身进行测试。至于junit怎样使用自身进行测试保证编写代码正确,这是一个有趣的问题,由于时间关系没有深入,留个坑,以后来填。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值