背景
为了能够让项目更加健壮,项目组要求大家在开发的过程中要写单元测试。在我写单元测试的过程中,想编写下面这个MessageService类的getUserMessageCount方法的单元测试:
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class MessageService {
private long getUserMessageCount(String userId, LocalDateTime begin, LocalDateTime end) {
//假装这里是一大堆的业务逻辑
//假装这里是一大堆的业务逻辑
//假装这里是一大堆的业务逻辑
//假装这里是一大堆的业务逻辑
//假装这里是一大堆的业务逻辑
return 100L;
}
}
但是不巧的是,这个方法是private的,在单元测试方法中,是无法直接通过MessageService对象去访问它的private方法。
方案
面对上述问题,首先想到了两个方案,不过都被我否决了:
1、 将getUserMessageCount的访问权限改成public。
2、 单元测试方法通过调用外层的public方法去调用进getUserMessageCount方法里。
方案1太暴力了,而且改变了原来项目的类方法的访问权限,虽然能解决问题,但是是一种很没节操的行为,会影响大家在这个项目上的开发过程。
方案2比起方案2有节操,但是在我这个项目上实际操作起来的话,要调用外层的public方法我要mock很多数据很不方便。
所以这两个方法马上就被我枪毙了。
突然想到平时我们开发过程中也有用到反射的方式去调用对象的某个方法,那能通过反射的时候调用private方法吗?答案当然是ok的!写了一个通用的通过反射调用对象private方法的工具类,如下:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectUtil {
/**
* 调用对象内指定的方法
*
* @param instant 对象实例
* @param methodName 方法名
* @param returnType 方法返回类型
* @param parameters 方法实际参数
* @param <T>
* @return
* @throws Throwable
*/
public static <T> T invokeNotPublicMethod(Object instant, String methodName,
Class<?> returnType, Object... parameters) throws Throwable {
Class<?>[] parameterTypes = null;
if (parameters != null && parameters.length > 0) {
parameterTypes = new Class<?>[parameters.length];
for (int i=0; i<parameters.length; i++) {
if (parameters[i] == null) {
throw new RuntimeException("数组parameters中的元素全都不能为null。如果必须为null,请使用方法:"
+ "com.huang.leecode.ReflectUtil.invokeNotPublicMethod(java.lang.Object, java.lang.String, java.lang.Class<?>, java.lang.Class<?>[], java.lang.Object[])");
}
parameterTypes[i] = parameters[i].getClass();
}
}
return invokeNotPublicMethod(instant, methodName, returnType, parameterTypes, parameters);
}
/**
* 调用对象内指定的方法
*
* @param instant 对象实例
* @param methodName 方法名
* @param returnType 方法返回类型
* @param parameterTypes 方法参数列表(形参)
* @param parameters 方法实际参数
* @param <T>
* @return
* @throws Throwable
*/
public static <T> T invokeNotPublicMethod(Object instant, String methodName,
Class<?> returnType, Class<?>[] parameterTypes, Object[] parameters) throws Throwable {
Method notPublicMethod = instant.getClass().getDeclaredMethod(methodName, parameterTypes);
//设置方法可访问
notPublicMethod.setAccessible(true);
T t =null;
try {
t = (T) notPublicMethod.invoke(instant, parameters);
} catch (InvocationTargetException e) {
//将反射调用方式时真正的异常跑出来
if (e.getTargetException() != null) {
throw e.getTargetException();
}
throw e;
}
return t;
}
}
思考
既然通过反射能直接访问对象的private方法,那为什么还要将类的方法设置为private?带着这个疑问去网上搜索了下相关的资料,总结如下:
JAVA访问权限的控制并不是为了安全而设计的,因为项目内的成员谁都可以改代码,改访问权限,所以和安全是不搭嘎的。访问权限的设计主要是为了OOP(面向对象编程)的封装概念。对于外部不需要直接访问的方法,我们没必要将他们暴露出去给外部,所以应该尽量将他们全部设置成private,封装在类内部。这对与类的使用的便捷性以及private方法的重构(因为是private方法,被调用的地方都在类的内部,重构起来很方遍,不需要担心有其他地方调用该方法)都有很大的意义。
这样子看来,我们在单元测试方法中使用反射的方式去调用目标要被测试的方法其实也不是一种很好的方式,万一目标方法的返回类型或者参数列表有变化,我们的单元测试就会失败了,最合理的方式还是要通过外层的public方法去调用内部的private方法来进行单元测试!
参考
通过反射访问private成员和方法,既然能访问为什么要private?
论Java访问权限控制的重要性
从《Java编程思想》提炼访问权限的重要性