Junit3代码分析

TestRunner.main是我们启航的地方:

public static void main(String args[]) {
    TestRunner aTestRunner= new TestRunner();
    try {
        TestResult r= aTestRunner.start(args);
        if (!r.wasSuccessful())
            System.exit(FAILURE_EXIT);
        System.exit(SUCCESS_EXIT);
    } catch(Exception e) {
        System.err.println(e.getMessage());
        System.exit(EXCEPTION_EXIT);
    }
}

从这里看出,TestRunner负责了对用户指定的测试类进行管理。它是进行测试的入口。

 

在此谈谈组合模式:

继续看TestRunner.start方法:
。。。

try {
    if (!method.equals(""))
        return runSingleMethod(testCase, method, wait);
    Test suite= getTest(testCase);
    return doRun(suite, wait);
} catch (Exception e) {
    throw new Exception("Could not create and run test suite: " + e);
}

继续看TestRunner.runSingleMethod方法:
Test test= TestSuite.createTest(testClass, method);
return doRun(test, wait);

所以,TestRunner的start方法与runSingleMethod方法,都会生成一个Test接口的实现类的实例,并运行doRun方法。在此先定个调,这里用了组合模式。start方法直接调用的doRun方法的suite参数是“树子”,是TestSuite对象,包括了指定的测试类所有的测试方法; 而runSingleMethod方法直接调用的doRun方法的test参数是“叶子”,是指定的测试类中被指定的方法名转换出来的TestCase对象.

Test接口的定义是这样的:
public interface Test {

    public abstract int countTestCases();

    public abstract void run(TestResult result);
}

Test接口的实现类TestSuite的定义是这样的:
public class TestSuite implements Test {
...
    private Vector<Test> fTests= new Vector<Test>(10);
...   
    public int countTestCases() {
        int count= 0;
        for (Test each : fTests)
            count+=  each.countTestCases();
        return count;
    }
...
    public void run(TestResult result) {
        for (Test each : fTests) {
              if (result.shouldStop() )
                  break;
            runTest(each, result);
        }
    }
   
    public void runTest(Test test, TestResult result) {
        test.run(result);
    }
...
}


Test接口的实现类TestCase的定义是这样的:
public abstract class TestCase extends Assert implements Test {
...
    public int countTestCases() {
        return 1;
    }
...
    public void run(TestResult result) {
        result.run(this);
    }
...
}

组合模式,无论是“树子”还是“叶子”,最终还会是调用“叶子”的方法。


看看“树子”TestSuite是怎么生成的:

继续看TestRunner的start方法, 它调用getTest方法:
public Test getTest(String suiteClassName) {
...
    return new TestSuite(testClass);
...
}
继续看TestSuite的构造方法:
public TestSuite(final Class<?> theClass) {
    addTestsFromTestCase(theClass);
}
继续看TestSuite的addTestsFromTestCase方法:
private void addTestsFromTestCase(final Class<?> theClass){
...
    Class<?> superClass= theClass;
    List<String> names= new ArrayList<String>();
    while (Test.class.isAssignableFrom(superClass)) {
        for (Method each : superClass.getDeclaredMethods())
            addTestMethod(each, names, theClass);
        superClass= superClass.getSuperclass();
    }
...
}
继续看TestSuite的addTestMethod方法:
private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
    String name= m.getName();
    if (names.contains(name))
        return;
    if (! isPublicTestMethod(m)) {
        if (isTestMethod(m))
            addTest(warning("Test method isn't public: "+ m.getName() + "(" + theClass.getCanonicalName() + ")"));
        return;
    }
    names.add(name);
    addTest(createTest(theClass, name));
}
继续看TestSuite的addTest方法:
public void addTest(Test test) {
    fTests.add(test);
}

就这样,TestSuite这棵树子生成了,它有很多“叶子”----TestCase对象. 叶子就放在fTests集合中。


看看每个测试方法(对应的TestCase对象)是如何被调用的:


继续看TestResult.run方法
protected void run(final TestCase test) {
    startTest(test);
    Protectable p= new Protectable() {
        public void protect() throws Throwable {
            test.runBare();
        }
    };
    runProtected(test, p);

    endTest(test);

}

继续看TestResult.runProtected方法
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.runBare方法
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;
}

看完这三条方法,有以下总结:
1.TestResult.run方法是由TestCase.run方法调用的。而最后是通过Protectable的匿名类实例再调用TestCase.runBare方法去运行TestCase的子类实例的测试方式.
2.TestResult.runProtected方法对TestCase.runBare方法抛出的异常作处理了。如何处理?请看异常体系部分。
3.从TestCase.runBare方法可以看出,TestCase子类的测试方法被执行前跟后,都会执行TestCase子类的setUP方法和tearDown方法。对于TestSuite来说,有多少个TestCase,setUp跟tearDown方法就会被调用多少次!!!!

这里有个很蛋痛的地方,干嘛要用Protectable来间接调用TestCase.runBare方法呢?想不通!难道是为了玩匿名类吗?


在此详细谈谈异常体系:
一、从TestRunner.start方法到调用TestResult.run方法前的阶段。
这阶段属于运行用户的TestCase子类任何方法前的准备工作. 存在抛Exception类型异常并且异常一起到main方法才被捕获的可能。
1. 在TestRunner.start方法内,
if (testCase.equals(""))
   throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
2. 在TestRunner.start方法内,有一个try catch 块。在try块中调用getTest方法,此方法很多处返回null值,当进入doRun方法调用suite.run(result)命令时,就会抛出NullPointerException了.这个异常会被start方法内的catch块捕获并转化成Exception实例,再往上抛。
3. 在TestRunner.start方法内,如果用户指定了测试类的测试方法名,就调用TestSuit.createTest方法创建Test,在这方法中如果产生异常,将捕获到的异常转换成TestCase的匿名类实例.该实例重写了runTest方法,一旦运行这方法,将会抛出AssertionFailedError类异常。当然,这异常抛出的时机是在下面的阶段产生。

所以,这个阶段抛出异常的时机是第1、2点处。

二、TestResult.run方法体开始的阶段。
1、异常的抛出地方是TestCase.runBare,TestCase.runTest.
runBare方法调用了三个存在抛异常可能的方法:setUp(),runTest(),tearDown()。
setUp方法执行时,由于用户编写TestCase子类并如果重写了setUp方法,那这个方法若抛出异常的话,这异常的类型是不确定的。所以这setUp方法产生的异常runBare方法就“放任”地让异常往上抛。
runTest方法通过反射,执行指定的测试方法。这里会产生若干个java反射机制固定的异常。junit只把其中的NoSuchMethodException转化成AssertionFailedError。另外,当测试方法运行时,它会产生的异常类型也是不确定的,这些异常在runTest方法内是“放任”地让它往上抛,来到runBare方法时,也是“放任”地让它往上抛。
tearDown方法情况跟setUP方法一样。不过它是在测试方法运行后才执行。它抛出的异常类型一样是不确定的。
2、捕获并处理异常的地方是TestResult.runProtected方法,它是直接调用TestCase.runBare方法的。
runProtected方法捕获的异常类型为AssertionFailedError,ThreadDeath,Throwable。关于AssertionFailedError,上文已经提到了。此处就把这类型的异常就入到TestResult的fFailures集合里。然后这异常就不再往上抛。
上文提到的setUP(),runTest(),tearDown()抛出的异常是类型是不确定的,如果属于ThreadDeadth类型的错误就往上抛,否则添加到TestResult的fErrors集合里并不再往上抛。所以在这方面中,能继续往上抛的异常就是ThreadDeadth类型错误。

所以,在这阶段,不会有异常抛到TestRunner.main方法了。有可能产生的ThreadDeadth类型的错误在main方法也不进行处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
导入HTMLTestRunner到Python,生成测试报告的工具类 """ A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance. The simplest way to use this is to invoke its main method. E.g. import unittest import HTMLTestRunner ... define your tests ... if __name__ == '__main__': HTMLTestRunner.main() For more customization options, instantiates a HTMLTestRunner object. HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g. # output to a file fp = file('my_report.html', 'wb') runner = HTMLTestRunner.HTMLTestRunner( stream=fp, title='My unit test', description='This demonstrates the report output by HTMLTestRunner.' ) # Use an external stylesheet. # See the Template_mixin class for more customizable options runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">' # run the test runner.run(my_test_suite) ------------------------------------------------------------------------ Copyright (c) 2004-2007, Wai Yip Tung All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值