JUnit 4.8 源碼解讀

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 框架源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章