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 fTests= new Vector(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 names= new ArrayList();
    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 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方法也不进行处理。

你可能感兴趣的:(Java阵营)