Junit源码阅读心得(1)

来源:互联网 发布:淘宝店铺怎么装修上新 编辑:程序博客网 时间:2024/05/29 19:45

大家好,这些天在一位前辈的建议下,开始阅读Junit源码,当然这个过程中,也有在参考他人的经验,有兴趣的朋友可以去看一下下面这位前辈的博文,相信可以给各位一些收获:JUNIT源码分析,下面和大家分享一些博主自己在阅读源码时的浅显心得,希望各位多多指教,不当之处,还望不吝赐教。
首先我们来依次说明几个Junit中的核心的类,分别为以下几个类和接口:

public interface Testpublic abstract class TestCase extends Assert implements Testpublic class TestSuite implements Test

以下是三个类的继承关系:

这里写图片描述
可以看到Test是作为接口存在,然后TestSuite与TestCase分别实现了Test接口,其中有两个方法:

public interface Test {    /**     * Counts the number of test cases that will be run by this test.     */    public abstract int countTestCases();    /**     * Runs a test and collects its result in a TestResult instance.     */    public abstract void run(TestResult result);}

其中方法countTestCases()可以获取到TestCase的数量,而run()方法则是运行Case,这里的TestCase可以简单理解为一个测试用例。
然后我们可以看到TestCase类还实现了Assert类,这样就可以获得一点优势,就是Assert类中的方法对于Case来说是完全透明的,使用TestCase的地方可以直接调用这些方法。
那这样的继承关系有什么好处呢?
首先我们来解释一下,这种继承关系在设计模式中叫做组合模式,TestSuite中可以组合多个TestCase用于测试:

public TestSuite(Class<? extends TestCase>[] classes, String name) {        this(classes);        setName(name);    }

同样我们也可以通过添加Test示例组合TestSuite:

// Cannot convert this to List because it is used directly by some test runnersprivate Vector<Test> fTests= new Vector<Test>(10); public void addTest(Test test) {        fTests.add(test);    }

也就是说只要实现了Test接口的实现类,都可以组合刀TestSuite中,当然,这其中也包括TestSuite自身,这样就形成了一个树状形式,在运行测试时,就可以逐层进行。
然后我们再来介绍一个类:

public class TestResult extends Object

TestResult中封装了测试运行结果,并作为返回结果,返回给Client,我们看一下其属性:

//测试失败    protected List<TestFailure> fFailures;//测试错误    protected List<TestFailure> fErrors;//监听器    protected List<TestListener> fListeners;

上面我们介绍了封装测试用例的TestCase和TestSuite以及其接口Test,并且介绍了封装测试结果的TestResult,那么有了结果,有了被执行对象,还缺少的就是测试执行者:

public class TestRunner extends BaseTestRunner

TestRunner就是测试执行者,我们在执行单元测试时,往往看到这样的注解:

@RunWith(SpringJUnit4ClassRunner.class)

这里就是指定测试执行的Runner,当然我们也可以不指定,那么默认的Runner就是:

public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod>

好了,我们接下来看一下TestRunner是怎么执行Test的:

static public TestResult run(Test test) {        TestRunner runner= new TestRunner();        return runner.doRun(test);}public TestResult doRun(Test test) {        return doRun(test, false);}public TestResult doRun(Test suite, boolean wait) {        TestResult result= createTestResult();        result.addListener(fPrinter);        long startTime= System.currentTimeMillis();        suite.run(result);        long endTime= System.currentTimeMillis();        long runTime= endTime-startTime;        fPrinter.print(result, runTime);        pause(wait);        return result;}

我们可以看到首先创建了TestRunner,然后使用Runner运行Test,首先创建了记录测试结果的TestResult对象,对记录测试结果,并向result中注册监听者,然后调用Test的run()方法进行测试,这个过程中涉及到另外的一个设计模式:观察者模式:
这里写图片描述
在这里我们向result中注册了我们的监听器,也就是对于Runner来说,扮演了观察者的身份,而result扮演了被观察者的身份,当result的状态变化时,可以通过观察媒介:监听器对Runner进行通知,或者说Runner通过监听器,监听到了result的变化,从而做出响应。
那么在这里的每个Test是怎么运行的呢?由于实现Test接口的类为TestSuite和TestCase,所以我们来看一下这两个类:
在TestSuite中:

public void run(TestResult result) {        for (Test each : fTests) {            if (result.shouldStop() )                break;            runTest(each, result);        }    }

我们可以看到,在TestSuite中依次调用了各个测试用例的封装来进行测试,也就是之前写到的树状结构,如果each为TestCase则调用TestCase,如果为TestSuite,则调用TestSuite,这样依次调用,直至全部调用完成。
接下来看一下TestCase:

/**     * Runs the test case and collects the results in TestResult.     */    public void run(TestResult result) {        result.run(this);    }    /**     * Runs the bare test sequence.     * @throws Throwable if any exception is thrown     */    public void runBare() throws Throwable {        Throwable exception= null;        setUp();        try {            runTest();        } catch (Throwable running) {            exception= running;        }        finally {            try {                tearDown();            } catch (Throwable tearingDown) {                if (exception == null) exception= tearingDown;            }        }        if (exception != null) throw exception;    }

这里涉及到TestResult中的run方法:

/*** Runs a TestCase.*/    protected void run(final TestCase test) {        startTest(test);        Protectable p= new Protectable() {            public void protect() throws Throwable {                test.runBare();            }        };        runProtected(test, p);        endTest(test);    }public void runProtected(final Test test, Protectable p) {        try {            p.protect();        }         catch (AssertionFailedError e) {            addFailure(test, e);        }        catch (ThreadDeath e) { // don't catch ThreadDeath by accident            throw e;        }        catch (Throwable e) {            addError(test, e);        }    }

我们看到在TestCase中,调用TestResult的run()方法,在TestResult中,我们注册了回调方法,也就是TestResult最终还是回调回了TestCase中的runBare()方法,同时我们看到在TestResult中的runProtected方法中,对各种类型的Throwable进行了捕获,也就是说当发生Throwable时,就会把对应的Throwable添加进我们前面说的三个List属性中。
而在TestCase中,真正执行Test的是runTest()方法:

protected void runTest() throws Throwable {        assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);        Method runMethod= null;        try {            // use getMethod to get all public inherited            // methods. getDeclaredMethods returns all            // methods of this class but excludes the            // inherited ones.            runMethod= getClass().getMethod(fName, (Class[])null);        } catch (NoSuchMethodException e) {            fail("Method \""+fName+"\" not found");        }        if (!Modifier.isPublic(runMethod.getModifiers())) {            fail("Method \""+fName+"\" should be public");        }        try {            runMethod.invoke(this);        }        catch (InvocationTargetException e) {            e.fillInStackTrace();            throw e.getTargetException();        }        catch (IllegalAccessException e) {            e.fillInStackTrace();            throw e;        }    }

在这里运用反射,根据存储的方法名称:fName找到要执行的方法,并予以执行,执行完毕后由TestResult做后续处理:endTest()方法:

public void endTest(Test test) {        for (TestListener each : cloneListeners())            each.endTest(test);}

在这里我们遍历了所有的监听器,并告知执行已完毕。然后TestCase返回TestResult。
好了,暂时就和大家分享这些,如有不足之处,还请多多指教。

原创粉丝点击