摘要: 用Commons Modeler 开发JMX应用 Modeler组件是Jakarta Commons 项目针对Model MBeans提供的一个便利的开发组件。 首先介绍一下基本的概念:Managed bean简称Mbean,是对可被管理的资源的抽象定义,ModelBean是JMX定义的Mbean中动态和灵活的一种。但是要实现它开发人员必须设置大量的Metadata信息。Modeler组件针对Mo...
阅读全文
单元测试的局限
关于单元测试的一篇好文章,我只翻译了一段,有兴趣的可以看看原文。
译自:http://www.theserverside.com/blogs/showblog.tss?id=Unitized
考虑一下单元测试的目的和原则:
1。尽量小粒度的“单元”被测试。
2。测试在于其它模块隔离地情况下独立地完成。
3。Mocking在隔离的方面作出了强化。
4。代码和测试都是同一个人完成的。
把上面提到的考虑在一起,意味着单元测试是测试整个代码中最低层次的部分,每一个部分是和其它部分隔离的。测试和编码是同一个人完成的。
这种方式的测试正是“我”所说的“low hanging fruit”[可轻易实现的目标 (easy targets)]。它可以捕获小的问题,也就是可以找到不符合单元测试的要求的单独的函数或者类。
单元测试是一个好事,提供了对于自己代码正确性的有价值的反馈。但是“Keep in mind" 它只能得到“low hanging fruit”。在设计上,单元测试被期望“简单的”、“作为系统中独立的小部分”。因为这些,在它的本质上(与生俱来的),单元测试没有考虑系统的“组合”,它只考虑独立的部分。单元测试从不检查一个系统中内部的联络,从不检查他们是如何捆绑在一起的。
根据“我”的经验,系统中如何联络和如何捆绑在一起正是系统的复杂度所在。
正是这种“连接”定义了你的设计,如果在一个足够高的层次上考虑,这种联系甚至可以理解为系统的架构。
信息是如何在不同的软件层上和不同的组件之间的流动实实在在的定义了一个应用。
由此看来,单元测试是不会测试一个应用的这些方面的。单元测试忽略了信息是如何在不同的层和不同的组件之间流动的,忽略了类和对象在一个大的架构和设计中如何相互关联和组合在一起的。
这就意味着单元测试只能在独立的代码片断中捕获简单的错误,但是对系统的整体的设计或者机构Say nothing。
设计和架构限定了你的系统的性能,内存使用,“端到端”的正确性。[用户的输入,到Server处理所使用的,到最后的返回的整个路径]。所以这些是如何进行联系的显示了系统的行为,并且正是在这个范围中存在着the toughest bug 和 problems,要想让一切OK,程序员们也要在这个地方苦干不止。
编写隔离的独立组件是容易的,把他们粘合在一起是困难的。单元测试只在隔离的在独立的部分上作判断,而不是在整体上。
使系统中的一个组件的动作正确相当来说是价值不高的活动。独立的编写一个系统的组件不是计算机编程的困难的部分,任何一个个体的小的组件都是容易编写的。在开发中最难的部分来自于如何把所有的组件捆绑在一起工作。单元测试可以验证每一个你编写的独立组件是不是按照你所想的那样工作,但是它不能检查更复杂的“wiring”--“wiring”正是我们的设计,开发和调试绝大部分工数所在。
从上看来,单元测试不会指出“端到端”的处理是否正常,不会关心性能,不会关心内存的使用,不会关心可用性,不会关心代码是否正是用户想要的。它也不会捕获多线程的bug,或者错误的理解了外部API或者子系统的使用等等。这些并不意味着单元测试是不好的或者应该避免的,它只是说明单元测试只会给你一个有限的回报。设想我们作为开发人员,我们没有无限的资源去开发我们的代码,我们不得不聪明的决定我们要把我们的精力放在那里。我们不得不经常的折衷和决定怎样做有最好的效果。
在“我”参加的所有开发中。单元测试覆盖了绝大部分的代码,但是在以下的方面的努力还差得很远:
1。应用程序设计的本身。你应该花费更多的时间在你的设计上,采用一种迭代的,真实地方式而不是花费在单元测试上,因为一个好的设计得到的回报比任何数量的单元测试都多。
2。集成测试(Integration Test)。集成测试的测试特征是基于“端到端”的。通过它的设计可以证明你的独立的组件可以工作在一起。通过一个集成测试,你可以更信赖你的系统按照“端到端”的方式工作,而不是一些独立的对象。
3。功能测试和回归测试(Functional Test&Regression Test)。系统不是开发人员想的那样,而是用户期望它是什么样子。更进一步,回归测试当新的特性被追加或者底层的代码被改变时,验证高级别的功能的正确性没有被改变。
4。非功能测试(Non-function Test)。在可接受的运行需求下,代码作为整体被运行,请求在可接受的时间范围内被处理。sever不会因为有3个用户请求就会memory紧张。等等。
做以上的东西会比单元测试难很多。但是在同样的投入下会得到更多的回报。
Java ClassLoader 实现程序的扩展性
今天在完成一个功能的时候,使用了ServiceLocate模式,
通过这个模式,在程序中可以自由的加载其他成员实现的功能模块
具体的做法:
1)定义标准的服务接口。
2)定义描述实现服务接口的xml文件。
3)程序读取该xml文件,使用Class.newInstance()实例化具体的服务对象。
4)建立一个特定服务和特定服务实现的对应的HashMap对象。完成注册任务。
5)主程序中根据具体的服务从HashMap中取得具体的对象进行服务。
这个方法还不错,可以完成基于Interface的开发要求,利于Test和程序的拓展性。
有新的要求出现后,只需要添加xml中的元素和具体的实现类就可以了。
接下来,继续想。又发现了一些问题:
1)xml是和程序一起发布的,如果用户随意改动了。很明显程序会崩溃。
解决方法:xml放在jar包中,使用getClass().getResourceAsStream(String name)
自己加载进来。用户完全不知道具体的情况。
2)如果把xml放在了jar包中“藏起来”,实际上原来带来的动态扩展的特性,
也就没有那么明显了。如何解决呢
细细想来,这个问题的关键在于,所有的服务实例对象的创建和注册都是在
主控程序中通过xml来完成的。如果可以把这个注册和实例化的过程从主控程序
中分离出来,通过每个服务实例对象自动注册来完成,那么才算是真正的可拓展的。
如果需要完成新的功能,只需要把新的服务对象Class发布,重新运行主控程序就会实现新的功能。(看起来就和Eclipse一样)
这真是一个不错的想法,但是怎么做呢?
看看Eclipse如何做的。
http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html
首先要有一个规定的plugin deploy目录,这样主程序才知道从哪里加载。
要有一个plugin.xml文件描述这个plugin.这着文件中有属性:class="foo.bar.Plugin">
看上去和我们原来做的方式一样啊。但是它是如何把这个目录下的plugin都加载的呢?
(我没有看过Eclipse的源码,不知道他是怎么写的)
再想想,其实主要要解决的问题是不通过主框架程序注册服务实现,
应该由服务程序自己注册上来。按照这个思路,我想有两种解决方案。
1)服务接口添加registerService 方法。
* 在jar的METATINFO文件中定义类名。
* 从特定的目录中读取jar/class文件。
* 通过URLClassLoader.newInstance()加载
* 加载后把ServiceLoader作为参数出入 service.registerSevice()中
* service对象完成自己的注册。
2)服务添加static代码端在类被实例化的时候自动完成注册。
* 加载之前和上一个方法一样。
* 对SeviceLoader对象实现为单态的模式。提供静态的注册方法。
* 在servie对象中实现如下的代码段完成自动注册。
static
{
ServiceLoader.registerService(new service());
}
这样看来总算OK了吧。
原来在http://a-ke.blogbus.com 发现太慢了。
自己上都费劲。转到这里会好一些吧!
回头把东西都搬过来。