Junit源码分析

Junit是非常短小精悍的单元测试框架,里面用到了大量的设计模式和设计原则,当然本文不是去分析这些模式,只是从头看代码分析一下它的执行过程:


1.Junit38ClassRunner的构造方法

        public JUnit38ClassRunner(Class<?> klass) {
                this(new TestSuite(klass.asSubclass(TestCase.class)));
        }



2.TestSuite的构造方法

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



   下面看一下addTestsFromTestCase(theClass)方法的源码

  
private void addTestsFromTestCase(final Class<?> theClass) {
                fName= theClass.getName();
                try {
                        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;
                }

                if (!Modifier.isPublic(theClass.getModifiers())) {
                        addTest(warning("Class "+theClass.getName()+" is not public"));
                        return;
                }

                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();
                }
                if (fTests.size() == 0)
                        addTest(warning("No tests found in "+theClass.getName()));
        }



然后我们再来看看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));
        }


isTestMethod(Method m)方法判断该方法是否为测试方法,即以test开头或者有相关注解的方法
addTest(createTest(theClass, name));这里将创建出TestCase的实例,并添加到fTests(Vector)中
这里创建的出的TestCase的fname属性将保存实际要测试的方法名


3.TestSuite的createTest方法

       
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+" ("+exceptionToString(e)+")"));
                } catch (InvocationTargetException e) {
                        return(warning("Exception in constructor: "+name+" ("+exceptionToString(e.getTargetException())+")"));
                } catch (IllegalAccessException e) {
                        return(warning("Cannot access test case: "+name+" ("+exceptionToString(e)+")"));
                }
                return (Test) test;
        }



下面我们进入Test的执行过程:

4.Junit38ClassRunner的run方法

  
public void run(RunNotifier notifier) {
         TestResult result= new TestResult();
         result.addListener(createAdaptingListener(notifier));
         getTest().run(result);
   }


   这里getTest()的得到的是TestSuite,上面的代码我们已经知道TestSuite中包含了多个TestCase实例,当然这里说的比较局限TestSuite也可以包含其他的TestSuite去执行一组测试,它们都保存在fTests中,这其实是组合模式,那接着我们就应该看看TestSuite的run方法

   5.TestSuite的run方法

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


  不出所料,在这里是遍历fTests去执行每一个TestCase实例的run方法,那我们就继续去看TestCase的run方法

  6.TestCase的run方法

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



  这里TestCase把自己作为参数传给TestResult的run方法去执行,这里为什么要这样做呢,为什么不直接执行呢?这个问题先留在这里,接着看

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


  startTest、endTest方法无非就是做一些初始化和销毁的工作,就不进入每个方法去看了,TestResult顾名思义是用来收集测试结果的,它在Runner、TestSuite、TestCase之间一直传递,保存所有测试方法的执行结果
  这里创建了一个受保护的Protectable类,在里面执行TestCase的runBare方法,runProtected()会去调用p.protect()方法.这说明测试方法实际上还是在TestCase中执行的,那为什么要传给TestResult,再在TestResult类中调用TestCase的runBare方法呢,我们来看一下runProtected()的具体过程吧
 
  8.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);
                }
        }


  很明显,这个过程是为了收集执行结果的,如果catch到AssertionFailureError表示测试结果不正确,如果catch到其他异常表示测试出现错误,addFailure和addError方法其实就是往TestResult的fFailures和fErrors列表中添加对象,并通知相应的观察者。

  我们回到主过程中,来接着看测试方法是如何被调用的:

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


 
  这里我们就会发现为什么junit在对每个方法进行测试时都要先后执行setUp()、实际要测试的方法和tearDown()方法,这里用的模板方法模式,setUp和tearDown如果在测试类中有进行复写,那实际执行的是测试类中的setUp和tearDown,而且测试每个类的前后都会执行这两个方法,而实际执行要测试的方法是在runTest()中实现的,下面就来看看runTest()方法吧

  10.TestCase的runTest()方法

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



   这里就可以发现Junit实际上就是把我们要测试的方法反射出来去invoke,以上这些就是Junit进行测试的全过程了,下面我们再来看分析前面的过程中遗留的问题:
   为什么在TestCase的run方法中不直接执行测试方法,而要将自身传给TestResult的run方法中去执行?
   这个问题通过后面的执行过程,大家应该也看明白了,这其实是Junit的设计上非常值得我们学习的地方,就是类的单一职责特点,TestCase里的方法应该都是测试相关的,而TestResult里的方法都是为了结果服务的,这个交织的过程就是为了获取测试结果

 

猜你喜欢

转载自guitar427.iteye.com/blog/1628420