解惑

解己之惑,解人之惑

标签:MockEJB

总算是初步完成了EJB单元测试框架

经过一段时间的努力,我总算是初步完成了这个EJB单元测试框架,现在运行那些测试用例(183个,难易不等)只需要60秒左右,我挑选出来的一个做为样例的Session Bean的代码行覆盖率达到94%,而Branch覆盖率为100%。
明天计划说服其它的人使用这个框架辅助他们的开发,虽然这个是单元测试,但是因为不用启动JBoss也可以调试Session Bean和Action,我想使用它辅助开发也是个不错的主意(我们的JBoss启动需要至少2分钟,一般都是在3分钟以上)。另外一个进行辅助开发的原因是我们的时间很紧,原本计划在这个版本中要求新的功能必须有单元测试的要求可能也要取消了,那我辛辛苦苦完成的框架可能要拖到至少明年3月以后才可能被使用了,夜长梦多啊。
另外一个考虑就是他们写的测试用例越多,可能发现的问题也越多,也好帮助我更加完善这个框架。另外就是可能会要求我增加更多的功能辅助他们的验证工作。

自主计算的超级计算机

人的大脑真的是自主计算的超级计算机,像前天一样,今天又是半宿没有睡着,半夜四点多醒了,然后就是在床上辗转反侧,到了五点的时候把老婆也吵醒了,不过还是一样,她很困,埋怨我不该把她闹醒,同时也不允许我起床用电脑,因为那样更吵。

这次同样还是性能问题,在成功的把性能提高20倍以后,我的大脑似乎并不满足,他还在寻找可以继续提高性能的地方,当然是有成果的了:

  • 性能的瓶颈在Aspect的查找(这个代码占了全部代码执行时间的一半,这个还是优化后的结果),自己实现了AspectSystem后,其实还可以以代理的方法作为Key把结果缓存起来。
  • 对Entity Bean之间的关系增加惰性初始化,因为其实很多时候代码使用的只是主要的那些属性(就测试用例代码而言),对其他的Entity Bean只是少数时候使用,为了保证每个测试用例之间是无关的,我必须每次都重新初始化,而且这个初始化的一系列的动作其实已经缓存了,但是在大部分情况下,这个关系没有必要初始化(这个从Entity Bean的一对多关系的BUG并没有导致我的测试用例全部失败就可以看出来),而且很多测试用例都是测试一部分的内容,下一个测试另外的部分,所以能够不做的尽量不做,实现也应该比较简单,增加一个BaseGetterAspect就可以了
  • 原来在决定那些方法是Setter的时候是遍历全部的方法,查找以set开头的,只有一个参数,并且类型是Wrapper类(Long、Integer、Boolean等)、String、Date、Collection、以及其他的Entity Bean,但是实际上从EJB的那些配置文件中已经解析到了所有的CMP的字段

大家常常说日有所思,夜有所梦,我做梦都在工作。但是平常做梦我都没有感觉(上大学以后到现在,每天的梦没有一个记得的),而这样的大脑自己高速运算推演把我也搞醒了。然后就是无法入睡,任凭大脑自己在那里写代码。

成功提高20倍性能

这两天其实一直在为性能问题发愁,忙得连日志都没有时间写,前天晚上想到一个解决性能问题的可能方案,结果半宿都没有睡着,脑袋一直在不由自主的想着代码怎么写,但是这个方案只是提高了一倍的性能,今天把代码覆盖的报告又仔细的看了一遍,发现有个方法竟然被调用了两千三百万次,虽然那个代码就是一个直接的return
一个成员变量的语句,但是拿到那个对象后MockEJB会和Aspect进行匹配,需要使用Apache ORO的正则表达式,这个不慢才怪。MockEJB默认使用AspectSystemImpl,它会遍历所有的Aspect,而我的某个测试用例可能会牵涉到20个以上的Entity Bean,每个Entity Bean有三个Aspect,然后调用EJB的每个方法都会这么遍历并且进行匹配,天啊!
没有办法,自己实现了一个AspectSystem,先把和我的工程无关的调用过滤掉,然后再根据调用的方法解析出对应的Entity Bean,从Map里面查找对应的Aspect(只有三个),这样速度一举提升10倍。刚才那个被调用两千三百万次的方法的调用次数也下降到四十万次左右。
还没有完,还是不满意,原来的那些Pointcut是使用的MockEJB自己带的一个实现类:MethodPatternPointcut,我写的那个表达式还是比较复杂的,想到我已经解析出来对应的Entity Bean了,我只关心方法名或者方法前缀就可以了,就自己实现了Pointcut。
这样修改以后性能又提升一倍。

代码如下:
阅读全文

条条大路通罗马

今天在写测试的用例的时候发现框架的一个Bug,CMP在初始化Entity Bean的时候会自动初始化相关的Entity Bean,但是如果是在一对多的情况下,首先使用的如果是一那端的情况下,不会自动的初始化多那一端的Entity Bean,后来添加了代码修正这个问题后,发现原来只要2分钟运行的所有的测试用例要15分钟才能运行完了,跟踪了一下发现是其中一个测试用例所使用的Entity Bean会连带初始化其他十几个Entity Bean,而且其中有几个Entity Bean的初始数据非常的多,每一个都有300条数据,这样要初始化好需要3分钟,而每个test方法都会来这么一次初始化。后来想着加Cache,Entity Bean填充好以后就缓存起来,后面的test方法再需要初始化的时候就直接进行对象拷贝就可以了,但是Entity Bean在使用的时候会修改一些值,这样如果发生变化的话应该从Cache里面清除,而且需要连带清除所有的Entity Bean。原来缓存的是MockEJB动态创建的Proxy对象,所以要进行对象复制不容易,看了MockEJB的源代码本来想自己也创建一个新的Proxy对象进行复制保存,但是工作量比较大,后来想到的解决的办法就是把对象的属性都复制到一个Map里面,主键是属性的名字,值就是对象值,如果值是集合类型,那么要创建一个同样的集合类并发那个集合里面的全部值加进去。

上面是我在公司的时候的解决方案,在写这篇日志的时候,又想到可能的其他方法,例如初始化Entity Bean的时候不初始化相关的其他的Entity Bean,只有在需要的时候才初始化(增加一个BaseGetterAspect,就像其他的Aspect一样);另外就是在发布Entity Bean的时候不初始化所有的Entity Bean实例,只有在需要使用到那个实例的时候再初始化。当然这两个也不是那么简单就可以实现的,明天还要再好好的考虑下哪种最保险最简单。

在遇到一个问题的时候,解决方案是多种多样的,只要你敢想。:)

基于MockEJB的CMP实现

MockEJB提供了一个简单的CMP容器实现,但是功能实在是过于简单,只实现了一个findByPrimaryKey方法。它的CMP其它功能的实现需要依赖于Aspect,我的这个单元测试框架就是靠实现了一堆的Aspect来完成CMP的一些功能的。

  1. BaseCreateAspect:调用entity bean的create方法会进入这个Aspect,如果是系统初始化从数据库和文件读取初始数据的话,会直接根据那些数据填充entity bean,如果是程序代码创建的entity bean,就会调用entity bean的create和postCreate并且准备把创建的entity bean的内容同步到数据库。
  2. BasePostCreateAspect:调用entity bean的postCreate会调用这个,目前功能不多。(没有必要,删除)
  3. BaseFindAspect:调用entity bean的find方法会调用这个,前面的finder解析器解析的结果就是由它来使用的。
  4. BaseRemoveAspect:调用entity bean的remove方法会调用这个,会把记录从数据库删除。
  5. BaseSetterAspect:调用entity bean的setter方法会调用这个,记录entity bean的内容有更新,可能会需要同步到数据库。

在完成这些Aspect的时候需要定义拦截点,可以使用表达式,可惜使用的是Apache的ORO的语法,和Java自己带的表达式语法不同。(修改为自己的Pointcut实现

代码如下:
阅读全文

艰难的过程

最近在做公司的单元测试框架,本来感觉已经初具规模,可以开始正常的使用了,因为我写了40多个各种类型的测试用例都可以正常工作,另外一个组的同事开始写了一个,他说还是挑的一个最简单的,结果报了一堆的异常,呵呵,其实他是测试Message Driven Bean,代码里面要调用很多其它的东西,出了几个问题,一个是他要使用的一个Entity Bean的finder是我写的表达式不支持的,我只能把那个finder实现掉,然后就是代码中使用了一个Topic,而且不是我们的Server端代码使用的,没有对应的Message Driven Bean处理(框架只能自动发布有对应的Message Driven Bean的Queue或者Topic),这个问题忽略不计了,因为那个Message本来就不需要处理,然后就是另外一个finder方法,我的表达式是支持的,但是死活找不到,结果发现是那个Entity Bean的xdoclet的配置比较特殊,一般的Home接口都是Remote接口的名字加Home,结果那个Bean是加BeanHome,而Aspect的匹配表达式是默认的格式,没有办法,修改我的表达式了,谁让那个Home接口的名字是可配置的呢。
另外两个问题还没有搞定,一个是Stateless Session Bean的remove方法,不知道要做什么,我现在处理了Entity Bean的remove,调用那个的时候会从数据库中删除对应的记录,但是Stateless Session Bean的remove应该做什么呢?
一个是获得Topic,我们的那个Locator接口里面定义了获得各种Bean以及Queue的接口方法,但是没有定义获得Topic的方法,所以代码中都是使用:
Context jndiContext = new InitialContext();
topic = (Topic) jndiContext.lookup(serverDataSendTopicName);
这个lookup的过程框架拦截不到。

MockEJB测试框架之自动发布EJB后续

本来今天已经完成了框架的大部分功能,写的一些测试用例都可以成功执行,但是自动发布是放在BaseTestCase里面的,感觉不好,就重构了一下,扩展了InitialContext类,覆盖了lookup方法(参数为字符串的那个,我们的代码中都是使用的这个),这样代码看起来更好一些的。另外我们的系统里面使用到了一些类似的机制,我为了产品可以进行单元测试还对一些Factory类进行了简单的修改,让他们返回我为了单元测试而写的实现类。如果使用这个扩展的InitialContext类,应该不需要做那些修改了,试了下,发现不行,因为产品的代码好像和Jboss的某些特性进行绑定了,先做简单的重构,以后再研究产品到底使用了JBoss的哪些特性,能否搞定。
代码如下:
阅读全文

MockEJB测试框架之自动发布EJB

前段时间大致介绍了下这个测试框架,目前还没有完全完成,这里先慢慢介绍下框架得构成,这个是第一篇。

其实MockEJB提供的功能还是比较简单的,做单元测试需要我们自己写代码发布Bean,例如发布一个Session Bean:
            SessionBeanDescriptor sampleServiceDescriptor =
                new SessionBeanDescriptor( SampleService.JNDI_NAME,
                SampleServiceHome.class, SampleService.class, new SampleServiceBean() );
             mockContainer.deploy( sampleServiceDescriptor );

其实每个EJB工程都应该有现成的EJB配置文件(ejb-jar.xml、jboss.xml、jbosscmp-jdbc.xml),因此这个发布工作应该可以自动完成,思路就是自己读取那些配置文件获得EJB的配置信息。
然后就是要修改lookup的过程,由于我们的工程提供一个共通的接口来进行lookup,所以问题就演变为实现一个那个接口。对于其它的项目,应该是类似的,如果是直接使用Context的lookup,那么对于MockEJB而言就是扩展MockContext,覆盖lookup方法,先查找,找到目标就返回,没有的话尝试根据读取的EJB配置信息自动发布需要的EJB。这样,我们就在单元测试中可以直接获取EJB,而不用再进行发布了。

EJB单元测试框架

公司的产品要引入单元测试以及代码覆盖,让我做一个框架方便我们写单元测试代码,上网找了一下,找到一个不错的基础:MockEJB,大家可以先了解下相关的内容:
MockEJB项目简介信息
使用MockEJB简化EJB测试

我在这个基础上进行了扩充,框架具有如下的特性:

  • 读取EJB配制文件完成EJB的自动发布,框架会读取我们工程的ejb-jar.xml,jboss.xml,jbosscmp- jdbc.xml文件的配制信息并组合这些信息完成自动发布所需要的内容。
  • 共享的dummy数据,我们自己提供一个xml文件,里面存放一些dummy的数据,这些数据会在对应的Entity Bean被使用的时候自动插入数据库,完成数据的初始化工作(这样Session Bean中的SQL也可以取到一致的内容),在一个test方法结束的时候回滚,删除那些记录,消除Test Case之间的影响。
  • 自定义事务管理,每个test方法都处于同一个事务中,方法结束的时候事务回滚,消除Test Case之间的影响。
  • 支持部分find方法的实现,由于MockEJB对于Entity Bean只提供了对findByPrimaryKey的支持,甚至连findAll都不支持,框架在MockEJB提供的Aspect机制的基础上实现了 findAll等常用必备的find方法,并且对于自定义的find方法也提供部分支持,通过解析EJB配制文件中的SQL完成,但是可能性能不好,因为会遍历所有的记录并比较字段的值完成这个查找的过程。其它比较复杂的find方法(框架无法解析的SQL),需要自己实现对应的Aspect,框架会自动查找对应的Aspect。
  • Struts TestCase的基础上支持对Action的单元测试。

测试代码范例:
阅读全文

© 2019 解惑

本主题由Anders Noren提供向上 ↑