最近对测试(集成测试+单元测试)代码进行了一下重构, 导致很多以前运行通过的测试现在都无法通过了, 由于我们的test case在测试前需要加载一堆spring配置文件, 而这个加载过程耗尽了99%的测试时间, 无尽的等待实在让人心烦. 虽然使用TestSuite可以对这些spring配置文件只需要加载一次, 但是如果需要对一个个的testcase进行单个调试修改的话, 就比较耗时了.
当然我们的测试也需要调整, 现在的spring配置文件耦合还是比较紧, 导致某一个测试需要依赖一些无关的bean, 虽然我们采用了spring bean的延迟加载策略, 但是依然是杯水车薪, 效果不是很明显.
好吧, 转入正题, 这里我们需要利用OSGi的动态加载和热替换特性, 将test case与spring container采用bundle的组织形式进行分离. 然后测试和spring container在OSGi容器中运行. 这样当我们需要对单个test case运行的时候就不再需要加载spring container了, 直接使用bundle中的spring container了.除非对spring container中的bean进行了修改需要重启加载spring container之外, 其他时候则不需要重启之. 对test case也采用同样的处理方式, 这样可以采用边改边测. 大幅度的提高了修改test case的效率.
下面说说大致做法
首先将spring 配置相关的东东使用一个bundle封装起来, 并对外提供一个类似这样的暴露接口:
public interface SpringContext {
public ApplicationContext getApplicationContext();
}
并实现一个对外提供服务的Activator:
public class SpringContextActivator implements BundleActivator {
private ServiceRegistration sf;
public void start(final BundleContext context) throws Exception {
sf = context.registerService(SpringContext.class.getName(), new SpringContextImpl(), null);
}
public void stop(BundleContext context) throws Exception {
sf.unregister();
}
}
然后将test case用另外一个bundle进行封装, 也就是将原有的工程转换成plug-in工程. 这里我们采用的测试框架是Unitils 3.1, 需要对SpringModule进行重载, 改写其中创建ApplicationContext的过程:
public class OsgiSpringModule extends SpringModule {
@Override
public boolean isApplicationContextConfiguredFor(Object testObject) {
return true;
}
@Override
public void invalidateApplicationContext(Class<?>... classes) {
}
@Override
public void init(Properties configuration) {
}
@Override
public ApplicationContext getApplicationContext(Object testObject) {
return SpringContextHolder.get();
}
}
然后提供一个执行指定测试类的Activator:
public class TestActivator implements BundleActivator {
private static final Log logger = LogFactory.getLog(TestActivator.class);
private ServiceReference sr;
public void start(BundleContext context) throws Exception {
sr = context.getServiceReference(SpringContext.class.getName());
SpringContext ctx = (SpringContext) context.getService(sr);
SprongContextHolder.set(ctx.getApplicationContext());
//执行指定测试类或方法...
}
public void stop(BundleContext context) throws Exception {
SpringContextHolder.remove();
}
}
怎么通过代码方式来执行指定的类和方法,
这里我有写, 可以参考下.
同时使用了一个SpringContextHolder, 用来在Activator和SpringModule之间传递ApplicationContext:
public class SpringContextHolder {
public static ThreadLocal<ApplicationContext> inner = new ThreadLocal<ApplicationContext>();
public static ApplicationContext get() {
return inner.get();
}
public static void set(ApplicationContext value) {
inner.set(value);
}
public static void remove() {
inner.remove();
}
}
另外使用到了DataSet, 而里面的测试数据的路径也需要改写, 以前采用是与测试文件同路径, 当采用OSGi之后, 路径全变了, 采用的是bundleresources协议的URL, 而原有test采用的是file协议的URL, 因此需要在二者之间进行一个转换.具体做法我已经在
OSGi小结里面进行了说明.
这些就是大致的做法, 里面还有一些细节需要注意, 否则很难跑起来:(