JUnit源码解读

  • Post author:
  • Post category:其他


JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个开源的单元测试框架。它属于白盒测试,只要将待测类继承 TestCase 类,就可以利用 JUnit 的一系列机制进行便捷的自动测试了。

JUnit 的设计精简,易学易用,但是功能却非常强大,这归因于它内部完善的代码结构。 Erich Gamma 是著名的 GOF 之一,因此 JUnit 中深深渗透了扩展性优良的设计模式思想。 JUnit 提供的 API 既可以让您写出测试结果明确的可重用单元测试用例,也提供了单元测试用例成批运行的功能。在已经实现的框架中,用户可以选择三种方式来显示测试结果,并且显示的方式本身也是可扩展的。

JUnit 的完整生命周期分为 3 个阶段:初始化阶段、运行阶段和结果捕捉阶段。下面就从这三个阶段详细分析源代码。

这里写图片描述


初始化阶段


初始化的入口有三个,分别是junit.textui.TestRunner,junit.swingui.TestRunner,junit.awtui.TestRun

ner,他们的不同就是ui不同,下面以textui为例:

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);
        }
    }
    public TestResult start(String[] args) throws Exception {
        String testCase = "";
        String method = "";
        boolean wait = false;
        ...
        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);
        }
    }

start函数所做的事就是将传入的参数中的类名提取出来(也就是你要测试哪个类)存在testcase中,并且根据你传入的参数确定运行模式,拿到testcase后,就调用getTest方法。

public Test getTest(String suiteClassName) {
        ...
        try {
            //首先根据类名获取Class信息
            testClass = loadSuiteClass(suiteClassName);
        ...
        try {
            suiteMethod = testClass.getMethod(SUITE_METHODNAME);
        } catch (Exception e) {
            // try to extract a test suite automatically
            clearStatus();
            //然后根据Class信息获取所有的测试方法
            return new TestSuite(testClass);
        ...
    }

如果这里没有通过getMethod方法拿到suiteMethod的话,就会调用TestSuite构造器

public TestSuite(final Class<?> theClass) {
        addTestsFromTestCase(theClass);
    }

    private void addTestsFromTestCase(final Class<?> theClass) {
        ...
        Class<?> superClass = theClass;
        List<String> names = new ArrayList<String>();
        //判断superClass是不是Test类的子类
        while (Test.class.isAssignableFrom(superClass)) {
            for (Method each : MethodSorter.getDeclaredMethods(superClass)) {
                addTestMethod(each, names, theClass);
            }
            superClass = superClass.getSuperclass();
        }
        if (fTests.size() == 0) {
            addTest(warning("No tests found in " + theClass.getName()));
        }
    }

用迭代的形式从被测试的类开始,到被测试的类继承的TestCase,最后到Object类,这个循环从这几个类中所有符合条件的测试方法加入到名为names的list中。

private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
        String name = m.getName();
        if (names.contains(name)) {
            return;
        }
        //判断是否是public方法
        if (!isPublicTestMethod(m)) {
            //判断是否是以test开头的函数
            if (isTestMethod(m)) {
                addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));
         ...
    }

点进addTestMethod方法中,可以发现,加入的方法,第一必须是public的,第二签名必须以test开头,形如testXXX()


测试运行阶段

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

初始化之后,开始运行

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

循环调用每一个测试的方法

runMethod.invoke(this);

最终由这行代码调用了被测试方法


结果捕捉阶段

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

将错误与失败加入链表中,然后由fPrinter打印在控制台上,这里使用的是观察者模式

参考资料:


https://www.ibm.com/developerworks/cn/java/j-lo-junit-src/#icomments



版权声明:本文为qq_19894073原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。