前言
在使用spring实际生产开发中,写完代码需要测试,如果不想写测试用例,也不想写controller来测试怎么办,经常一些非http服务测试会十分麻烦,如果要写controller或者是单元测试去测,需要大量的时间,当然时间足够还是写单元测试好一些,但是懒人就是不想写,然后又想测试完善以免测试不完全。本文提供一个比较好的操作去解决这些问题。
代码编写
这里用一个controller方法去暴露一个http接口,通过反射和spring的通过bean name去找bean示例,来实现一个接口测所有方法。首先,定义一个beanFactory来获取bean
@Component
public class BeanFactoryTest implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
}
然后定义一个controller方法:
/**
*
* @param beanName 调用的bean的名字
* @param methodName 调用方法名
* @param paramsClassName 调用方法入参的class name
* @param params 调用方法的入参String
* @return 结果
* @throws ClassNotFoundException 不管
* @throws InvocationTargetException 不管
* @throws IllegalAccessException 不管
* @throws NoSuchMethodException 不管
*/
@RequestMapping("/testAll")
@TryLogin
public String testAll(String beanName,String methodName,String[] paramsClassName,String[] params) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
//根据名字获取bean示例对象
Object object = beanFactoryTest.getApplicationContext().getBean(beanName);
//创建入参class数组
Class[] argsClass = new Class[paramsClassName.length];
//创建入参obj数组
Object[] paramObj = new Object[paramsClassName.length];
for (int i = 0; i < paramsClassName.length; i++) {
//用自写的class forName通过类名去获取class
argsClass[i] = aClassForName(paramsClassName[i]);
//巧妙操作,通过fastJson来做类型转换,实测所有类型可转,int,long这种基本类型也可以
paramObj[i] = JSONObject.parseObject(params[i], aClassForName(paramsClassName[i]));
}
//通过方法名,和参数类型class,找到方法
Method method = object.getClass().getMethod(methodName,argsClass);
assert method != null;
//取消权限检查,可以调用private
method.setAccessible(true);
//执行方法
Object result = method.invoke(object, paramObj);
//返回执行结果,用json序列化后显示
return JSONObject.toJSONString(result);
}
这里面aClassForName是重写的一个通过类名去查找class,因为java基本类型无法通过Class.forName来查找,Class.getPrimitiveClass又是protect方法,所以自己写一个。代码十分简单,如下:
public Class<?> aClassForName(String name) throws ClassNotFoundException {
switch (name){
case "int":
return int.class;
case "boolean":
return boolean.class;
case "long":
return long.class;
case "byte":
return byte.class;
case "short":
return short.class;
case "float":
return float.class;
case "double":
return double.class;
case "char":
return char.class;
default:
return Class.forName(name);
}
ok,完事了,就是这么简单。
测试及注意事项
如图,直接测试redis的一个sevice。原方法如下
public Long incr(String key,long step,long expire){
Long value = stringRedisTemplate.opsForValue().increment(key,step);
stringRedisTemplate.expire(key,expire, TimeUnit.SECONDS);
return value;
}
可以从上图截图中看到方法有返回,我连续请求调用了两次,返回了2。
这里需要注意的是使用 JSONObject.parseObject吧任何类型从String转为某种类型,如果是int,long,Long,之类的直接写就行,如果是map,set,list,或者是自己定义的类,用json规范写就ok,需要特别注意的是String类型的话,这一步就是String转String,所以String应该是这种格式"\"i am a String\""
。这里是postman里面,可以不用反斜杠转码。
到这里你有没有发现,这不仅仅是个测试接口,更是个可以调用任意bean方法的后门,用反射原理,随便再改改,什么方法都能调,什么实例都能改。
最后劝告,生产别直接带上去,配置开关或者权限,或者不要带上。