问题简单描述
原Java类中函数调用其私有变量,写单元测试的时候没办法直接获取原Java类中私有变量
具体情况
1.原Java类代码(删除了与问题无关的代码,不影响具体描述)
public class ChargeBlock implements Parcelable {
private int startHour1;//早些的时间段起始小时数,比如0
@Override
public String toString() {
return "ChargeBlock{" +
"startHour1=" + startHour1 +
", endHour1=" + endHour1 +
", startHour2=" + startHour2 +
", endHour2=" + endHour2 +
'}';
}
public int getStartHour1() {
return startHour1;
}
public void setStartHour1(int startHour1) {
this.startHour1 = startHour1;
}
}
2.问题具体分析
toString()函数中调用了ChargeBlock类中的私有变量startHour1,在一开始写testToString()函数的时候考虑的是直接在测试的时候调用对应的getxxx()函数,但是又想到此时getxxx()函数并没有被测试,不能默认getxxx()函数是不会出错的,所以选择反射调用获取私有变量,然后进行测试
3.具体测试代码
class ChargeBlockUT {
private ChargeBlock target;
//此处把获取私有变量提取出来作为一个单独的函数,可以降低代码重复性。
//targetClassInstance:原Java类的对象(在调用fieldSet()函数前需要声明)
//declaredField:被测试类中的私有变量的变量名
//targetObject:是实际要设置字段值的对象实例
//newValue:要设置到targetObject中指定字段的新值
//声明了两个可能抛出的异常:NoSuchFieldException(如果指定的字段不存在)和IllegalAccessException(如果应用程序没有适当的访问权限来设置该字段的值)
private void fieldSet(Object targetClassInstance, String declaredField, Object targetObject, Object newValue) throws NoSuchFieldException, IllegalAccessException {
//获取被测试类中私有变量值
Field field = targetClassInstance.getClass().getDeclaredField(declaredField);
//确保可以访问私有字段,Java的反射API默认不允许访问私有字段,除非明确设置。
field.setAccessible(true);
//使用field.set(object, obj)将obj(新值)设置到object(目标对象)的declaredField(指定字段)中
field.set(targetObject, newValue);
}
@BeforeAll
public static void beforeAll() {
...
}
@AfterAll
public static void afterAll() {
...
}
@BeforeEach
public void beforeEach() {
closeable = MockitoAnnotations.openMocks(this);
target = new ChargeBlock();
}
@AfterEach
public void afterEach() {
try {
closeable.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void testToString() throws NoSuchFieldException, IllegalAccessException {
//声明获得chargeBlock,以便调用fieldSet函数
//PREPARE
ChargeBlock chargeBlock = new ChargeBlock();
fieldSet(chargeBlock, "startHour1", target, 1);
fieldSet(chargeBlock, "endHour1", target, 2);
fieldSet(chargeBlock, "startHour2", target, 3);
fieldSet(chargeBlock, "endHour2", target, 4);
//INVOKE
String toString = target.toString();
//ASSERT
Assertions.assertEquals(toString, "ChargeBlock{startHour1=1, endHour1=2, startHour2=3, endHour2=4}");
}
}
存在问题
在提交代码的时候,有AI审查结果(识别可改善项):使用反射操作私有字段: 代码中大量使用反射来访问和修改私有字段,这违反了封装性原则,可能导致安全问题。
但这个审查我忽略了,因为不这么处理的话需要将私有字段更改为受保护的,测试人员没有权限更改原Java类代码(或许也没有必要。。。)
技术细节
Java中反射的调用,此处只是调用私有变量,私有函数也可以调用,使用方法也很简单
反射原理理解比较难,但是实际写代码的时候很容易,使用Android studio写的很多时候都有提示。
以下是学习反射的时候看的文章(其实没怎么理解)
https://blog.csdn.net/weixin_74268571/article/details/131345164
小结
1.被测试类中私有变量在被测试函数中调用,测试函数应该如何去处理这些私有变量
2.测试函数处理私有变量的具体过程最好提出来作为一个函数,使用到反射调用的时候直接调用此函数,降低大块代码重复性(这是之后遇到的问题,刚写的时候遇到的被测试类中代码重复性不是很高。。)
3.写完所有测试任务后这确实是一个很小的问题,但是刚接触测试的时候也是思考了不少时间,在这里记录下自己测试实习的第一步。