spring module的初始化还算简单, 但是熟悉里面的原理有利于搭建一个项目的测试脚手架.
spring module 通过[b]ApplicationContextManager[/b]来管理所有测试的ctx
而获取指定测试类的ctx, 使用了下面的方法:
[b]ApplicationContextManager[/b]会用一个map来存储所有test类的ctx
分别在class, field, method上面找SpringApplicationContext注解(这个找的实现逻辑是:
在class上只找本类的, 在field会在继承结构上继续往上找所有的注解, 而method上只找本类), 但是三者只允许存在一个注解:
从所有注解上拿到spring bean配置文件列表
然后在方法上找是否加了[b]SpringApplicationContext注解[/b](只在本类方法上找)
在所有的使用了SpringApplicationContext注解的方法上过滤掉没有返回值(void)的方法
所有的最终使用SpringApplicationContext注解方法只能有一个, 否则抛异常
如果本类没有找到的话, 再到父类上去找(如果可以的话).
这些方法必须满足条件[quote](T createMethodName() or T createMethodName(List<String> values))[/quote] 这里的T就是ctx类型
如果本类中使用了在方法(这个方法称之为customCreateMethod)上使用SpringApplicationContext注解, 那么通过反射拿到ctx, 因为前面在类(方法, field)上拿到了一个配置文件列表, 这里会传这个列表给方法作为参数(如果需要的话)
如果在本类的class(field, method)上指定了spring配置文件, 但是本类没有在方法上使用SpringApplicationContext注解, 则到父类上面去找方法, 而且这个过程会一直追踪到最上面的基类, 直到找到这样的方法. 因此这里需要特别注意, 在整个继承结构中只允许一个在方法上使用SpringApplicationContext注解, 因为一旦找到了这个方法, 将不再继续往上查找. 找到方法, 完成ctx的创建过程.
如果在整个测试类的继承结构中都没有找到所需要的方法, 则将使用ApplicationContextFactory创建一个ctx. spring module 默认是ClassPathXmlApplicationContextFactory, new的ClassPathXmlApplicationContext. 如果你需要定制的话, 可以通过unitils.properties替换掉unitils的实现.
如果本类没有使用SpringApplicationContext注解, 那么在测试父类上继续找, 直到找到为止.
完成之后调用下面的方法来做一些后续的操作.
最后是以testclass为key, ctx为value进行缓存以便下次重用.另外还有一个需要注意的是如果出现继承层次太多, 而在不同的测试层次上加上SpringApplicationContext注解, 可能会导致找不到基类的配置的问题(这个主要是因为对配置文件的查找, 在class, field上只能查找本类, 在method可以在父类上找, 但是找到之后就不在继续往上找, 加上三者只能使用一个注解).
通过对spring module初始化源码解读, 可以了解到使用配置文件的一个最佳实践(在unitils上也有说明), 在基类上这样写:
然后可以在子类的class上打上SpringApplicationContext注解, 给出当前测试类所需要的配置文件.
这样配置的一个好处是, 如果一起同时跑多个测试类的话, 每个测试的spring配置都会单独的在manager的map中缓存一份儿, 对我们目前成百个测试类的应用有可能会导致内存溢出, 这的确是个严重的问题.解决办法就是所有的测试都配置在基类, 从而共享同一份spring bean xml files, 但是这样对跑单个测试来说, 又会出现初始化耗时太长的问题(根据我们的测试发现初始化spring配置文件几乎占了整个测试时间的90%多), 因此需要作出权衡.
spring module 通过[b]ApplicationContextManager[/b]来管理所有测试的ctx
而获取指定测试类的ctx, 使用了下面的方法:
org.unitils.core.util.AnnotatedInstanceManager.getInstanceImpl(Object testObject, Class<?> testClass)
[b]ApplicationContextManager[/b]会用一个map来存储所有test类的ctx
protected Map<Class<?>, T> instances = new HashMap<Class<?>, T>();
分别在class, field, method上面找SpringApplicationContext注解(这个找的实现逻辑是:
在class上只找本类的, 在field会在继承结构上继续往上找所有的注解, 而method上只找本类), 但是三者只允许存在一个注解:
org.unitils.core.util.AnnotatedInstanceManager.getAnnotationValues(Class<?> testClass)
从所有注解上拿到spring bean配置文件列表
org.unitils.spring.util.ApplicationContextManager.getAnnotationValues(SpringApplicationContext annotation)
然后在方法上找是否加了[b]SpringApplicationContext注解[/b](只在本类方法上找)
org.unitils.core.util.AnnotatedInstanceManager.getCustomCreateMethod(Class<?> testClass, boolean searchSuperClasses)
在所有的使用了SpringApplicationContext注解的方法上过滤掉没有返回值(void)的方法
所有的最终使用SpringApplicationContext注解方法只能有一个, 否则抛异常
如果本类没有找到的话, 再到父类上去找(如果可以的话).
这些方法必须满足条件[quote](T createMethodName() or T createMethodName(List<String> values))[/quote] 这里的T就是ctx类型
如果本类中使用了在方法(这个方法称之为customCreateMethod)上使用SpringApplicationContext注解, 那么通过反射拿到ctx, 因为前面在类(方法, field)上拿到了一个配置文件列表, 这里会传这个列表给方法作为参数(如果需要的话)
如果在本类的class(field, method)上指定了spring配置文件, 但是本类没有在方法上使用SpringApplicationContext注解, 则到父类上面去找方法, 而且这个过程会一直追踪到最上面的基类, 直到找到这样的方法. 因此这里需要特别注意, 在整个继承结构中只允许一个在方法上使用SpringApplicationContext注解, 因为一旦找到了这个方法, 将不再继续往上查找. 找到方法, 完成ctx的创建过程.
如果在整个测试类的继承结构中都没有找到所需要的方法, 则将使用ApplicationContextFactory创建一个ctx. spring module 默认是ClassPathXmlApplicationContextFactory, new的ClassPathXmlApplicationContext. 如果你需要定制的话, 可以通过unitils.properties替换掉unitils的实现.
如果本类没有使用SpringApplicationContext注解, 那么在测试父类上继续找, 直到找到为止.
完成之后调用下面的方法来做一些后续的操作.
unitils.core.util.AnnotatedInstanceManager.afterInstanceCreate(T instance, Object testObject, Class<?> testClass)
最后是以testclass为key, ctx为value进行缓存以便下次重用.另外还有一个需要注意的是如果出现继承层次太多, 而在不同的测试层次上加上SpringApplicationContext注解, 可能会导致找不到基类的配置的问题(这个主要是因为对配置文件的查找, 在class, field上只能查找本类, 在method可以在父类上找, 但是找到之后就不在继续往上找, 加上三者只能使用一个注解).
通过对spring module初始化源码解读, 可以了解到使用配置文件的一个最佳实践(在unitils上也有说明), 在基类上这样写:
private static ApplicationContext parent;
@SpringApplicationContext
public ApplicationContext createApplicationContext(List<String> locations) {
ApplicationContext parent = createParentApplicationContext(locations);
ctx = new FileSystemXmlApplicationContext(locations.toArray(new String[locations.size()]), parent);
return ctx;
}
private static ApplicationContext createParentApplicationContext(List<String> locations) {
if (parent == null) {
synchronized (parent) {
String[] parentLocations = new String[] {
"classpath:config.xml",
"classpath:jdbc.xml",
"classpath:mock.xml"
};
parent = new FileSystemXmlApplicationContext(locations.toArray(parentLocations)) ;
}
}
return parent;
}
然后可以在子类的class上打上SpringApplicationContext注解, 给出当前测试类所需要的配置文件.
这样配置的一个好处是, 如果一起同时跑多个测试类的话, 每个测试的spring配置都会单独的在manager的map中缓存一份儿, 对我们目前成百个测试类的应用有可能会导致内存溢出, 这的确是个严重的问题.解决办法就是所有的测试都配置在基类, 从而共享同一份spring bean xml files, 但是这样对跑单个测试来说, 又会出现初始化耗时太长的问题(根据我们的测试发现初始化spring配置文件几乎占了整个测试时间的90%多), 因此需要作出权衡.