`
jgnan
  • 浏览: 87546 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

JUnit4.7学习笔记(一)——核心分析

阅读更多
单元测试一直以来都是让程序员又爱又恨的家伙。一方面它可以给我们提供最细粒度的单元测试,保障我们代码重构时的质量。另一方面它会增加我们的工作量,可以说一个不完整的单元测试就跟没有测试差不多。而且要写一个有效的单元测试,除了要花费大量时间之外,更加需要丰富的单元测试经验和编码经验。

而且最重要的是,我们其实不怎么了解单元测试的核心——JUnit这个工具。

俗话说,工欲善其事,必先利其器。今天开始我们就来了解一下这个工具。看了下经典的Junit In Action,已经是很古老的版本。自从Junit发布到4.1版本以后,单元测试的写法就已经有了划时代的变化,大量使用标记以及基础框架更加灵活和可定制,已经成为Junit的特点。虽然本人很久以前曾经整理过Junit4.1的学习笔记,不过很不幸由于本人自己的疏忽已经不知去向,无奈之下只好重新整理最新版本4.7的学习笔记,并且借此机会跟大家分享一下。

因为这是学习笔记,所以更多会注重代码的分析和一些应用点评,所以不是具体的使用教学,如果大家想问这方面的问题可以单独和我联系,我会把我的相关经验都和大家分享。

首先我们来分析一下JUnit4.7的核心。我们从测试的入口开始去了解Junit是怎么去跑测试的。从调试模式中我们发现以下的类会被涉及到org.junit.runner.JUnitCore这个类。

JUnitCore就是Junit的入口,执行Junit跑测试的就是它。它里面提供了各种跑测试的方法,其中实际使用的方法包括

public Result run(Class<?>... classes)
public Result run(Computer computer, Class<?>... classes)
public Result run(Request request)
public Result run(junit.framework.Test test)


其中前2个方法都会调用第三个方法run(Request request),而最后一个方法用于执行JUnit3.8风格的测试用例。

那么,来看一下Request这个抽象类。我们发现,大部分的测试都会被封装为一个具体的Request,而由Request根据类的类型来决定应该用什么Runner来执行这组测试。其中关键的一个方法就是:
public abstract Runner getRunner();


除了这个方法以外,Request还提供很多帮助我们把一个东西封装为Request的方法:
public static Request method(Class<?> clazz, String methodName)
public static Request aClass(Class<?> clazz)
public static Request classWithoutSuiteMethod(Class<?> clazz)
public static Request classes(Computer computer, Class<?>... classes)
public static Request classes(Class<?>... classes)
public static Request errorReport(Class<?> klass, Throwable cause)
public static Request runner(final Runner runner)


以方便我们使用。

那么Request是怎么知道我们需要用什么Runner来跑测试的呢?Runner又是用来做什么工作的呢?从上面的封装方法中,我们查看classes方法,发现里面做了这些事:
public static Request classes(Computer computer, Class<?>... classes) {
    try {
        AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true);
        Runner suite= computer.getSuite(builder, classes);
        return runner(suite);
    } catch (InitializationError e) {
        throw new RuntimeException(
                "Bug in saff's brain: Suite constructor, called as above, should always complete");
    }
}


好,留意到这个叫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;
}


原来,Request决定应该用什么Runner这个工作是交由RunnerBuilder决定的,而默认的RunnerBuilder就是这个AllDefaultPossibilitiesBuilder。它在里面定义了所有的默认RunnerBuilder列表,然后通过逐个调用RunnerBuilder.safeRunnerForClass方法来看当前的测试类是否适用这个RunnerBuilder所构建的Runner。由于junit4BUilder是最后一个判断的Builder,也说明它是默认的Builder,那么我们就来看看这个方法具体做了些什么:
protected JUnit4Builder junit4Builder() {
    return new JUnit4Builder();
}


然后看JUnit4Builder这个类的safeRunnerForClass方法,发现这个方法默认是从RunnerBuilder这个抽象类中继承下来的,而其中调用的runnerForClass方法由子类实现,具体的代码为:
public Runner runnerForClass(Class<?> testClass) throws Throwable {
    return new BlockJUnit4ClassRunner(testClass);
}


然后我顺藤摸瓜,从BlockJUnit4ClassRunner的构造函数发现具体调用的ParentRunner的构造函数:
protected ParentRunner(Class<?> testClass) throws InitializationError {
    fTestClass= new TestClass(testClass);
    validate();
}


然后再看到TestClass的构造函数如下:
public TestClass(Class<?> klass) {
    fClass= klass;
    if (klass != null && klass.getConstructors().length > 1)
        throw new IllegalArgumentException(
                "Test class can only have one constructor");

	......
}


好了,我们发现判断是否使用BlockJunit4ClassRunner的标准,首先当然就是它不符合其它Builder类型的构造另外我们发现只要它的类的构造函数为1就可以了,也就是测试类只允许有一个构造函数。

在按这个道理分析一下junit3Builder,发现其判别条件为:
package org.junit.internal.builders;

import org.junit.internal.runners.JUnit38ClassRunner;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;

public class JUnit3Builder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class<?> testClass) throws Throwable {
    if (isPre4Test(testClass))
            return new JUnit38ClassRunner(testClass);
    return null;
}

    boolean isPre4Test(Class<?> testClass) {
        return junit.framework.TestCase.class.isAssignableFrom(testClass);
    }
}


再看JUnit38ClassRunner的构造函数:
public JUnit38ClassRunner(Class<?> klass) {
    this(new TestSuite(klass.asSubclass(TestCase.class)));
}


先会判断它是否继承了TestCase,然后会按照以前的要求来判断是否符合测试用例的规格。如果都符合了,就会采用这个Runner来跑测试。

所以,现在的测试用例,不需要像以前教科书说的那样,需要继承TestCase来实现了,而只需要是一个Class就好。以下就是一个具体的测试用例例子:
public class Junit4LikeTestCase
{
    //test methods
    ....
}

就可以了!!而且先透露一下下一节的内容,其实一个测试方法,只要这样写就OK:
@Test public void test(){
//Test Method Block
...
}


好了,这样我们就可以知道整个框架的核心概念:
类名意义
org.junit.runner.JUnitCore跑测试的前端程序,JUnit的入口
org.junit.runner.Request用于封装测试请求,并且匹配具体请求和Runner
org.junit.runner.Runner具体测试执行者
org.junit.runner.RunnerBuilder再Request中根据测试类生成对应Runner的构造器

以上的列表就说明了整个Junit中的核心组件。后面我们会围绕后三个核心进行深入的剖析,也就是这个学习笔记系列的主要内容。
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

Global site tag (gtag.js) - Google Analytics