`
guitar427
  • 浏览: 4720 次
  • 性别: 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  
沙发,写的真好!

相关推荐

    航空公司客户满意度数据转换与预测分析Power BI案例研究

    内容概要:本文档介绍了航空公司的业务分析案例研究,涵盖两个主要部分:a) 使用SSIS进行数据转换,b) 利用RapidMiner进行预测分析。这两个任务旨在通过改善客户满意度来优化业务运营。数据来源包括多个CSV文件,如flight_1.csv、flight_2.csv、type.csv、customer.csv 和 address.csv。第一部分要求学生创建事实表、客户维度表和时间维度表,并描述整个数据转换流程。第二部分则需要利用RapidMiner开发两种不同的模型(如决策树和逻辑回归)来预测客户满意度,并完成详细的报告,其中包括执行摘要、预测分析过程、重要变量解释、分类结果、改进建议和伦理问题讨论。 适合人群:适用于对数据科学和商业分析有一定基础的学生或专业人士。 使用场景及目标:本案例研究用于教学和评估,帮助学员掌握数据转换和预测建模的技术方法,提高客户满意度和业务绩效。目标是通过实际操作加深对相关工具和技术的理解,并能够将其应用于实际业务中。 其他说明:此作业占总评的40%,截止时间为2024年10月25日16:00。

    课题设计-基于MATLAB平台的图像去雾处理+项目源码+文档说明+课题介绍+GUI界面

    一、课题介绍 现在我国尤其是北方城市,工业发达,废弃排放严重,这使得雾霾越来越厉害,让能见度极低。这严重影响了我们的交通系统,导航系统,卫星定位系统等,给人民出行,工作带来极大的不便利。目前市场上高清拍摄设备虽然可以让成像清晰点,但是造价高昂。如果有一套软件处理系统,可以实时地处理含雾的图像,让成像去雾化,让图像变得清晰,将会很受欢迎。 该课题是基于MATLAB平台的图像去雾处理,配备一个人机交互GUI界面,可以选择全局直方图均衡化,Retinex算法,同态滤波,通过对比处理前后的图像的直方图,而直方图是一副图像各灰度值在0-256的分布个数的表,信息论已经整明,具有均匀分布直方图的图像,其信息量是最大的。 二、算法介绍 ①全局直方图均衡化:通俗地理解就是,不管三七二十一,直接强行对彩色图像的R,G,B三通道颜色进行histeq均衡处理,然后进行三通道重组; ②Retinex算法:通俗地讲就是,分离R,G,B三通道,对每个通道进行卷积滤波。

    微信支付V2版本的支付接口,java的SDK

    微信支付V2版本的支付接口,java的SDK

    ide-eval-resetter-2.1.14 无限试用插件

    一款IDEA好用的插件,适用于旗舰版,可以延长试用期限,你懂的!

    电力系统继电保护整定及其应用-发电机组与变压器保护

    内容概要:本文详细介绍了发电机组保护整定方法,讨论了发电机可能遇到的故障状态及相应的保护措施,包括定子绕组故障、转子绕组故障、过电流、过电压等情况,并提供了具体的保护配置。接着,对变压器常见故障进行了分类说明,并给出变压器的主保护和后备保护配置方案。文章进一步计算了不同短路点的短路电流,阐述了互感器的选择标准,并举例解释了纵联保护的应用和后备保护的作用。 适合人群:电力系统工程师、继电保护技术人员及相关研究领域的学者。 使用场景及目标:适用于发电厂和变电站的设计、维护和运行人员,旨在提高电力系统的安全性和稳定性。目标是确保电力系统关键设备的安全运行,防止故障的发生,减少事故造成的损失。 其他说明:本文不仅提供了详细的理论分析,还包括了大量的数据计算和实例说明,有助于读者更好地理解和掌握继电保护的相关知识和技术。

    基于C++开发的微商系统项目源码.zip

    基于C++开发的微商系统项目源码 使用技术:C++/Qt、Mysql、TCP/IP 软件架构 三个大端:服务端、客户端(买家端)、业务端(卖家端) PS:需要连Mysql数据库才能正常使用,连接mysql时记得把用户、密码啥的改为自己的数据库信息

    1503ANDH1503002016_20241116222825

    1503ANDH1503002016_20241116222825

    数理逻辑近世代数复习资料,思维导图部分

    数理逻辑近世代数复习资料,思维导图部分

    京东中台业务架构敏捷性方法 MotriDSP 落地实践.pdf

    京东中台业务架构敏捷性方法 MotriDSP 落地实践.pdf

    毕业设计&课设_电影推荐系统项目:Spring Boot 开发,含多种技术,有前端地址,提供开发及部署说明.zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    CCF会员复习资料(1).zip

    CCF会员复习资料(1).zip

    C#ASP.NET养老院老人信息管理系统源码数据库 SQL2008源码类型 WebForm

    ASP.NET养老院老人信息管理系统源码 这是一款非常优秀的养老院老人信息管理系统。程序功能齐全,可选择性强。 管理员后台: 1、人员信息管理:老人信息管理、管理员信息管理、员工信息管理 2、公寓信息管理:添加公寓楼信息、管理公寓楼信息、添加公寓信息、管理公寓信息 3、公寓安排管理:添加公寓安排、公寓安排管理、缴费管理 4、健康管理信息管理:添加健康管理信息、管理健康管理信息 5、紧急联络人管理 6、意见信息管理 7、公告信息管理:添加公告信息、管理公告信息 8、出入信息管理:请假信息管理、访客记录管理、退住情况 员工后台: 1、人员信息管理:老人信息管理 2、健康管理信息管理:管理健康管理信息 3、紧急联络人管理 4、意见信息管理 5、公告信息管理:管理公告信息 6、出入信息管理:请假信息管理、访客记录管理、退住情况 老人后台: 1、修改个人信息 2、修改登陆密码 3、意见信息管理:发布意见信息、意见信息管理 4、查看公告信息

    走向现代化数据分析架构:趋势与挑战.pdf

    走向现代化数据分析架构:趋势与挑战.pdf

    PHP通用权限管理系统源码数据库 MySQL源码类型 WebForm

    PHP通用权限管理系统源码 安装说明: 1.把管理系统.sql 导入到MYSQL数据库中,把App/Common/Conf/db.php中的数据库名称,账号密码改成自己的。 2.把程序放在二级目录下,如:http://localhost/xcrm/ 账号 admin 密码admin123 3.请使用IE8以上,或是谷歌浏览器。 4.支持环境php+apache,php版本要大于5.3 注意:删除App下面 Runtime 文件夹;如果Linux服务器,需要分配777权限; 修改config.php数据库连接地址:绝对路径 大家在安装的时候遇到问题 1.页面顶部出现空白一行 解决办法:db.php 要以uft8无BOM格式编码 保存 notepad++ 编辑器里 格式 下 2.乱码问题 解决办法:数据库建表的时候选 uft8-general-ci 编码格式 然后点开 SQL 把1,SQL复制进去,执行就可以了 3、如果程序执行时报错? 解决办法:保存PHP>5.3版本 删除App下面 Runtime 文件夹 使用须知:注释:权限管理,修改完用户所属组之后,

    各省电商指数数据(1990-2022).xlsx

    详细介绍及样例数据:https://blog.csdn.net/m0_65541699/article/details/143823092

    会议席卡制作模版,电子表格,方便实用

    席卡制作模版会议较多的单位,经常要打印席卡,本模版可以解决燃眉之急

    从 0 到 1 构建集群服务质量运营体系降低云成本.pdf

    从 0 到 1 构建集群服务质量运营体系降低云成本.pdf

    永辉超市的混合云建设与运维.pdf

    永辉超市的混合云建设与运维.pdf

    java超市订单管理系统源码数据库 MySQL源码类型 WebForm

    Java超市订单管理系统源码 运行环境:jdk8(jdk7)+mysql+Eclipse+maven+tomcat7 项目技术:springboot+spring mvc+mybatis+jquery+jsp 注意事项:1.所有模块没有实现查看,编辑,删除,添加功能 2.压缩包中的建库smbms.sql是mysql5.7的语句,其中datetime比较特殊,在低版本mysql下会报错

    基于MATLAB语言的支持向量机(svm)分类算法的机器视觉程序,里面包含训练数据集和MATLAB源码下载即可运行,可做毕业设计

    基于MATLAB语言的支持向量机(svm)分类算法的机器视觉程序,里面包含训练数据集和MATLAB源码,下载即可运行,可做毕业设计。

Global site tag (gtag.js) - Google Analytics