`
guitar427
  • 浏览: 4547 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

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里的方法都是为了结果服务的,这个交织的过程就是为了获取测试结果

 

 

分享到:
评论
5 楼 lvwenwen 2013-08-15  
顶,支持!
4 楼 feiMQ 2012-08-12  
3 楼 logcos 2012-08-10  

good job!
2 楼 bluesky5566 2012-08-10  
,顶,支持!
1 楼 wyfn18 2012-08-10  
沙发,写的真好!

相关推荐

    JUnit源码分析.doc

    JUnit源码分析.docJUnit源码分析.docJUnit源码分析.docJUnit源码分析.docJUnit源码分析.doc

    Junit源码分析(圣思园)

    NULL 博文链接:https://lvwenwen.iteye.com/blog/1934965

    junit源码解析之runner

    junit框架,基于源码分析runner实现原理

    feed4junit源码

    它能够从业务分析人员定义好的CVS或 Excel文件读取测试用例数据并在构建/单元测试框架中报告测试成功。利用Feed4JUnit能够很方便用随机但校验过的数据执行冒烟测试来提高代码 代码覆盖率和发现由非常特殊的数据结构...

    JUnit设计模式分析

    JUnit设计模式分析 详细讲解JUnit源码中用到的设计模式

    Junit设计模式分析(带源码)

    Junit设计模式分析(带源码)

    JUnit设计模式分析及简化的JUnit代码

    JUnit 是一个优秀的Java 单元测试框架,由两位世界级软件大师Erich Gamma 和 Kent Beck 共同开发完成。本文将向读者介绍在开发JUnit 的过程中是怎样应用设计模式的。

    JUnit学习资料

    包括JUnit.in.Action中文版.pdf,JUnit详解.pdf,Manning - JUnit in Action.pdf,单元测试之道Java版:使用JUnit.pdf以及Junit设计模式分析(Junit设计模式分析.pdf及源码实例),是学习JUnit不可多得的资料。

    junit-4.11

    junit是java自带的自动化测试工具,本资源包含了jar包及其源码,需要做源码分析的朋友可自行下载

    JUnit -- 分析

    NULL 博文链接:https://orange5458.iteye.com/blog/1305042

    java编程之单元测试(Junit)实例分析(附实例源码)

    主要介绍了java编程之单元测试(Junit),结合实例形式较为详细的分析总结了Java单元测试的原理、步骤及相关注意事项,并附带了完整代码供读者下载参考,需要的朋友可以参考下

    Struts-menu源码分析

    北京火龙果软件工程技术中心好的代码读起来让人... 一段广告完毕,下面就为大家分析一下struts-menu的源码,作为送给大家的圣诞礼物吧。Struts-Menu也来自一位大师的作品,MattRaible。有很多优秀的作品,比如使用stru

    基于JUnit4的关于个人所得税计算的等价类与边界值分析黑盒测试和路径覆盖白盒测试

    本文档中源码为软件测试课程实验相关内容,压缩包内也有详细需求说明。大致功能为测试一个输入为税前工资输出为税后工资的方法的单元测试,通过设计测试用例实现等价类划分测试,边界值分析测试以及路径覆盖测试

    jdk1.8-source-analysis:JDK1.8源码分析

    jdk1.8-source-analysis JDK1.8源码分析引入原始过程中的注意事项JDK1.8对应JDK版本下载: 码:49wi原始码在src目录下以下两个类手动添加的,解决编译过程中该包的丢失sun.font.FontConfigManager sun.awt....

    Spring高级之注解驱动开发视频教程

    视频详细讲解,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 1、课程简介 Spring框架是一系列应用框架的核心,也可以说是整合其他应用框架的基座。...n 源码分析-TransactionSynchronizationManager

    Collections源码java-jdk1.8-source-analysis:Java8源码分析,J.U.C、ThreadPool、Col

    JDK1.8源码分析 导入源码过程中的注意事项 JDK1.8对应JDK版本下载: 提取码:49wi 源码在src目录下 以下两个类手动添加的,解决编译过程中该包的丢失 sun.font.FontConfigManager sun.awt.UNIXToolkit 其中: 1.请...

    java8集合源码分析-java-demos:java-演示

    集合源码分析 java-demos other collect github project leetcode springCloud [Spring Cloud 从入门到实战] () [全网最详细的一篇SpringCloud总结] () [feign] () [Spring Security 真正的前后分离实现] () [Spring...

    java8集合源码分析-Project:工程目录

    集合源码分析 To Be Top Javaer - Java工程师成神之路 主要版本 更新时间 备注 v1.0 2019-04-10 首次发布 一、基础篇 面向对象 什么是面向对象 平台无关性 值传递 封装、继承、多态 Java基础知识 基本数据类型 自动...

    jdk1.8-source-analysis:Java 8源码分析,JUC,ThreadPool,Collection

    JDK1.8源码分析 引入原始过程中的注意事项 JDK1.8对应JDK版本下载: 码:49wi 原始码在src目录下 以下两个类手动添加的,解决编译过程中该包的丢失 sun.font.FontConfigManager sun.awt.UNIXToolkit 其中:1.请...

Global site tag (gtag.js) - Google Analytics