刚刚看了下代码覆盖的结果,发现如果一个类太小,那么覆盖率很容易达不到要求,一般而言,代码覆盖率要求85%左右,但是如果一个类只有十几行代码,特别是再包含一些try catch语句,那么代码覆盖率很容易只有70%以下,因为有时候很多try catch只是JAVA要求进行处理,程序实际运行不可能出那些异常。
虽然这样考虑有点舍本逐末的嫌疑,但是在有硬性指标的时候,这个也是一个需要考虑的因素。
刚刚看了下代码覆盖的结果,发现如果一个类太小,那么覆盖率很容易达不到要求,一般而言,代码覆盖率要求85%左右,但是如果一个类只有十几行代码,特别是再包含一些try catch语句,那么代码覆盖率很容易只有70%以下,因为有时候很多try catch只是JAVA要求进行处理,程序实际运行不可能出那些异常。
虽然这样考虑有点舍本逐末的嫌疑,但是在有硬性指标的时候,这个也是一个需要考虑的因素。
有感于这次单元测试框架的设计实现过程,决定慢慢的造一个大轮子:一个自己的web框架,初步计划是自己用,不考虑太通用,目的就是简单快捷的创建web应用,可以很方便的增加功能,基本上就是像ror一样简单快捷。
当然这个框架不会全部重写,肯定会基于现在的一些开源的组件搭建。但是肯定会排除spring和hibernate,因为这两个对于我而言是很大的轮子了。我需要的功能相对简单,我更喜欢基于规则而不是基于配置,很多东西其实没有必要配置,定义一套规则遵循就可以了。
初步拟定可能要使用到的一些组件是:
今天无意中又浏览了一下代码覆盖的结果,竟然让我无意中发现了一个用其它方法可能不会发觉的BUG。
代码片断如下:
/**
* This method is performance key point of this framework.
* @see org.mockejb.interceptor.AspectSystem#findInterceptors(java.lang.reflect.Method, java.lang.reflect.Method)
*/
public List findInterceptors(Method proxyMethod, Method targetMethod)
{
List resultList = new ArrayList();
List otherResult = find(proxyMethod, targetMethod, otherAspectList);
resultList.addAll(otherResult);
if (proxyMethod != null)
{
String proxyMethodString = proxyMethod.toString();
String targetMethodString = targetMethod.toString();
List aspect = CacheUtil.getBeanAspects(proxyMethodString + "-" + targetMethodString);
if (aspect != null)
{
resultList.addAll(aspect);//168
}
else
{
if (isARMethod(proxyMethodString, targetMethodString))
{
String beanName = getBeanName(proxyMethodString, targetMethodString);
if (beanName != null)
{
List aspects = (List) beanAspectMap.get(beanName);
if (aspects != null)
{
aspect = find(proxyMethod, targetMethod, aspects);
CacheUtil.putBeanAspects(proxyMethodString, aspect);
resultList.addAll(aspect);
}
}
}
}
}
return resultList;
}
注意标记为168的那一行,那个地方是Cache的功能,但是代码覆盖报告指出它没有被执行过,也就是那个Cache从来没有生效,因为原来的cache的key只是proxyMethod.toString(),后来为了在初始化的时候屏蔽ejbPostCreate的执行加了一些Aspect,但是ejbPostCreate和ejbCreate的proxyMethod是一样的,只是targetMethod不同。
这个BUG只影响性能,所以其它的方法是不可能发现问题的。
在完成这个EJB单元测试框架的CMP的时候,核心应该就是成功使用Command模式,框架在初始化的时候会自动初始化需要使用的Entity Bean,包括Entity Bean之间的依赖关系,但是如果在初始化的时候,一个Entity Bean依赖另外一个还没有初始化的Entity Bean,而所依赖的那个Entity Bean又依赖这个Entity Bean,就会形成循环依赖。我使用Command模式,讲这个依赖关系放到一个Command里面,然后在一个Entity Bean的基本属性初始化完成以后再执行Command队列初始化那些关系,就不会形成循环依赖了。
代码如下:
阅读全文
经过一段时间的努力,我总算是初步完成了这个EJB单元测试框架,现在运行那些测试用例(183个,难易不等)只需要60秒左右,我挑选出来的一个做为样例的Session Bean的代码行覆盖率达到94%,而Branch覆盖率为100%。
明天计划说服其它的人使用这个框架辅助他们的开发,虽然这个是单元测试,但是因为不用启动JBoss也可以调试Session Bean和Action,我想使用它辅助开发也是个不错的主意(我们的JBoss启动需要至少2分钟,一般都是在3分钟以上)。另外一个进行辅助开发的原因是我们的时间很紧,原本计划在这个版本中要求新的功能必须有单元测试的要求可能也要取消了,那我辛辛苦苦完成的框架可能要拖到至少明年3月以后才可能被使用了,夜长梦多啊。
另外一个考虑就是他们写的测试用例越多,可能发现的问题也越多,也好帮助我更加完善这个框架。另外就是可能会要求我增加更多的功能辅助他们的验证工作。
这两天其实一直在为性能问题发愁,忙得连日志都没有时间写,前天晚上想到一个解决性能问题的可能方案,结果半宿都没有睡着,脑袋一直在不由自主的想着代码怎么写,但是这个方案只是提高了一倍的性能,今天把代码覆盖的报告又仔细的看了一遍,发现有个方法竟然被调用了两千三百万次,虽然那个代码就是一个直接的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容器实现,但是功能实在是过于简单,只实现了一个findByPrimaryKey方法。它的CMP其它功能的实现需要依赖于Aspect,我的这个单元测试框架就是靠实现了一堆的Aspect来完成CMP的一些功能的。
在完成这些Aspect的时候需要定义拦截点,可以使用表达式,可惜使用的是Apache的ORO的语法,和Java自己带的表达式语法不同。(修改为自己的Pointcut实现)
代码如下:
阅读全文
其实我们很早就想搞培训了,去年也筹备过一次,后来不了了之了。这次在David的努力下总算是有些眉目了,只要报名的人数足够,近期就可以开始了。我也会讲一部分内容,第五天的那些杂乱的内容,期望能够讲好。以前没有搞过,不知道能不能讲好,但是只要准备充分,应该问题不大吧。
希望David能够尽快招到人,顺便也在这里做个广告。
© 2025 解惑
本主题由Anders Noren提供 — 向上 ↑