JUnit 4.8 源码解读

来源:互联网 发布:指南针炒股软件诈骗: 编辑:程序博客网 时间:2024/05/15 23:52

JUnit 4.8 源码解读

首先从junit-team/junit4下载源码。

JUnit的源码分成了2个大的package,junit和org.junit。junit包实现了基础的功能。其中junit目录是JUnit 3.X的内容,org.junit是后来JUnit 4.X新加的。先来讲讲junit。

程序员在写完TestCase后,右击Run As JUnit后,IDE会从TestCase的RunWith注解决定使用哪个Runner来运行TestCase,默认是TestRunner。

public class TestRunner extends BaseTestRunner {...    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);        }    }}

在main方法中,先新建一个TestRunner。这里会将系统输出作为参数传给构造器,生成一个ResultPrinter用于显示测试结果。

    public TestRunner() {        this(System.out);    }

然后执行

TestResult r = aTestRunner.start(args);
public TestResult start(String[] args) throws Exception {        String testCase = "";        String method = "";        boolean wait = false;        for (int i = 0; i < args.length; i++) {            if (args[i].equals("-wait")) {                wait = true;            } else if (args[i].equals("-c")) {                testCase = extractClassName(args[++i]);            } else if (args[i].equals("-m")) {                String arg = args[++i];                int lastIndex = arg.lastIndexOf('.');                testCase = arg.substring(0, lastIndex);                method = arg.substring(lastIndex + 1);            } else if (args[i].equals("-v")) {                System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma");            } else {                testCase = args[i];            }        }        ...    }

start方法读取了命令参数。-wait是会等用户输入,-c是指示TestCase的全类名,-m指定了要跑的方法,-v用来输出当前JUnit的版本,或者直接跟一个类名。

testCase = args[i];
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);        }

获取到方法名和类名后,如果有方法,则调用runSingleMethod(testCase, method, wait),否则调用getTest(testCase);

public Test getTest(String suiteClassName) {        if (suiteClassName.length() <= 0) {            clearStatus();            return null;        }        Class<?> testClass = null;        try {            // Class.forName(suiteClassName);            testClass = loadSuiteClass(suiteClassName);        } catch (ClassNotFoundException e) {            String clazz = e.getMessage();            if (clazz == null) {                clazz = suiteClassName;            }            runFailed("Class not found \"" + clazz + "\"");            return null;        } catch (Exception e) {            runFailed("Error: " + e.toString());            return null;        }        Method suiteMethod = null;        try {            //获取 suite 方法            suiteMethod = testClass.getMethod(SUITE_METHODNAME);        } catch (Exception e) {            // try to extract a test suite automatically            clearStatus();            // 如果没有suite方法,就生成一个TestSuite            return new TestSuite(testClass);        }        // suite方法必须是static        if (!Modifier.isStatic(suiteMethod.getModifiers())) {            runFailed("Suite() method must be static");            return null;        }        Test test = null;        try {            // 调用suite方法,返回方法返回值            test = (Test) suiteMethod.invoke(null); // static method            if (test == null) {                return test;            }        } catch (InvocationTargetException e) {            runFailed("Failed to invoke suite():" + e.getTargetException().toString());            return null;        } catch (IllegalAccessException e) {            runFailed("Failed to invoke suite():" + e.toString());            return null;        }        clearStatus();        return test;    }

通常情况下,我们不会去实现suite方法,所以这里讲新建TestSuite。

初始化TestSuite

public TestSuite(final Class<?> theClass) {        addTestsFromTestCase(theClass);    }    private void addTestsFromTestCase(final Class<?> theClass) {        fName = theClass.getName();        try {            // 先尝试获取带一个String参数的constructor,如果没有,则获取无参constructor            getTestConstructor(theClass); // Avoid generating multiple error messages        } catch (NoSuchMethodException e) {            addTest(warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()"));            return;        }        // TestCase类必须是public的        if (!Modifier.isPublic(theClass.getModifiers())) {            addTest(warning("Class " + theClass.getName() + " is not public"));            return;        }        // 嵌套获取TestCase类的测试方法        Class<?> superClass = theClass;        List<String> names = new ArrayList<String>();        // 判断父类是否继承了Test接口              while (Test.class.isAssignableFrom(superClass)) {            for (Method each : MethodSorter.getDeclaredMethods(superClass)) {                //                 addTestMethod(each, names, theClass);            }            superClass = superClass.getSuperclass();        }        if (fTests.size() == 0) {            // 如果没有满足的test method,则生成一个fail的测试用例            addTest(warning("No tests found in " + theClass.getName()));        }    }
private void addTestMethod(Method m, List<String> names, Class<?> theClass) {        String name = m.getName();        // 只添加最新的同名方法        if (names.contains(name)) {            return;        }        // isTestMethod(m) && Modifier.isPublic(m.getModifiers());        // 公有方法,且是test method        if (!isPublicTestMethod(m)) {            // 必须满足三个条件:方法无参,方法名以test开头,没有返回值            if (isTestMethod(m)) {                addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));            }            return;        }        names.add(name);        addTest(createTest(theClass, name));    }
static public Test createTest(Class<?> theClass, String name) {        Constructor<?> constructor;        try {            constructor = getTestConstructor(theClass);        } catch (NoSuchMethodException e) {            return warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()");        }        Object test;        try {            if (constructor.getParameterTypes().length == 0) {                test = constructor.newInstance(new Object[0]);                if (test instanceof TestCase) {                    ((TestCase) test).setName(name);                }            } else {                test = constructor.newInstance(new Object[]{name});            }        } catch (InstantiationException e) {            return (warning("Cannot instantiate test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));        } catch (InvocationTargetException e) {            return (warning("Exception in constructor: " + name + " (" + Throwables.getStacktrace(e.getTargetException()) + ")"));        } catch (IllegalAccessException e) {            return (warning("Cannot access test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));        }        return (Test) test;    }

如果是这么addTest的话,假设一个TestCase下有3个方法,那AddTes就会加3个对象,有点浪费。到此处TestSuite就初始化好了。

运行TestCase

    public TestResult start(String[] args) throws Exception {        ...            return doRun(suite, wait);       ...    }    public TestResult doRun(Test suite, boolean wait) {        // 新建一个TestResult        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;    }
    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);    }

运行TestCase

下面是junit.framework.TestCase.run方法,这里以TestResult调用了run方法。

    public void run(TestResult result) {        result.run(this);    }

在TestResult的run方法中,分为三部分,startTest,runProtected,endTest。

    protected void run(final TestCase test) {        startTest(test);        Protectable p = new Protectable() {            public void protect() throws Throwable {                test.runBare();            }        };        runProtected(test, p);        endTest(test);    }

在startTest方法中,记录了运行的testcase数量,并且调用了TestListener的startTest方法。TestListener有四个接口,分别是addFailure, addError, startTest, endTest。

    public void startTest(Test test) {        final int count = test.countTestCases();        synchronized (this) {            fRunTests += count;        }        for (TestListener each : cloneListeners()) {            each.startTest(test);        }    }

runProtected方法,就是调用Protected接口里的protect方法。其实就是上面的test.runBare()。AssertionFailError就是Assert失败抛出的异常。

    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方法,定义了运行测试方法的顺序,首先是setUp,然后是runTest,最后是tearDown。因为tearDown是在finally中,所以tearDown是一定会运行的。

    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;    }

TestCase的runTest方法运用java的反射,调用了测试方法。并对

    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;        }    }

在测试用例跑完后,TestListener的endTest会被触发。

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

最后TestRunner会调用fPrinter的print方法打印出结果。

参考文档:
1.分析 JUnit 框架源代码

0 0