目录
如今关于私有方法是否应该做单元测试的争论正逐渐消停,开发者的普遍实践已经给出事实答案。通过公有方法间接测私有方法在很多情况下难以进行,开发者们更愿意通过修改方法可见性的办法来让原本私有的方法在测试用例中变得可测。
此外,在单元测试中时常会需要对被测对象进行特定的成员字段初始化,但有时由于被测类的构造方法限制,使得无法便捷的对这些字段进行赋值。那么,能否在不破坏被测类型封装的情况下,允许单元测试用例内的代码直接访问被测类的私有方法和成员字段呢?TestableMock
提供了两种简单的解决方案。
下面我们通过案例直接讲解一下如何使用。
第一种方法是借助PrivateAccessor
工具类来直接访问私有成员。这个类提供7个静态方法:
PrivateAccessor.get(任意对象, "私有字段名")
➜ 读取任意类的私有字段;PrivateAccessor.set(任意对象, "私有字段名", 新的值)
➜ 修改任意类的私有字段(或常量字段);PrivateAccessor.invoke(任意对象, "私有方法名", 调用参数...)
➜ 调用任意类的私有方法;PrivateAccessor.getStatic(任意类型, "私有静态字段名")
➜ 读取任意类的静态私有字段;PrivateAccessor.setStatic(任意类型, "私有静态字段名", 新的值)
➜ 修改任意类的静态私有字段(或静态常量字段);PrivateAccessor.invokeStatic(任意类型, "私有静态方法名", 调用参数...)
➜ 调用任意类的静态私有方法;PrivateAccessor.construct(任意类型, 构造方法参数...)
➜ 调用任意类的私有构造方法;特别说明:默认情况下,
setStatic()
方法不支持修改static final
修饰的成员变量。在Java中此类变量通常代表业务意义上的恒定常量值,不应当在单元测试中更改。
【a】编写被测试类
package com.wsh.testable.mock.testablemockdemo;
import java.util.List;
/**
* 演示私有成员访问功能
*/
public class DemoPrivateAccess {
/**
* 私有的静态变量
*/
private static int staticCount;
/**
* 私有成员变量
*/
private int count;
/**
* 常量
*/
public final Double pi = 3.14;
/**
* 私有静态方法、无参
*/
private static String privateStaticFunc() {
return "static";
}
/**
* 私有静态方法、有参
*/
private static String privateStaticFuncWithArgs(String str, int i) {
return (str == null ? "null" : str) + " + " + i;
}
/**
* 私有成员方法、无参
*/
private String privateFunc() {
return "member";
}
/**
* 私有成员方法、有参
*/
private String privateFuncWithArgs(List<String> list, String str, int i) {
return list.stream().reduce((a, s) -> a + s).orElse("") + " + " + str + " + " + i;
}
}
【b】编写测试类
package com.wsh.testable.mock.testablemockdemo;
import com.alibaba.testable.core.tool.PrivateAccessor;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 演示使用`PrivateAccessor`工具类访问私有成员
*/
class DemoPrivateAccessorTest {
/**
* 被测试类实例
*/
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
/**
* 1. 访问私有成员方法
* PrivateAccessor.invoke(任意对象, "私有方法名", 调用参数...) ➜ 调用任意类的私有方法
*/
@Test
void should_access_private_method() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
add("c");
}};
//调用私有成员方法、无参
assertEquals("member", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc"));
//调用私有成员方法、有参
assertEquals("abc + hello + 1", PrivateAccessor.invoke(demoPrivateAccess, "privateFuncWithArgs", list, "hello", 1));
}
/**
* 2.访问私有成员字段
* PrivateAccessor.get(任意对象, "私有字段名") ➜ 读取任意类的私有字段
* PrivateAccessor.set(任意对象, "私有字段名", 新的值) ➜ 修改任意类的私有字段(或常量字段)
*/
@Test
void should_access_private_field() {
//修改私有成员变量count的值
PrivateAccessor.set(demoPrivateAccess, "count", 3);
//获取私有成员变量count的值
assertEquals(Integer.valueOf(3), PrivateAccessor.get(demoPrivateAccess, "count"));
}
/**
* 3.访问私有静态方法
* PrivateAccessor.invokeStatic(任意类型, "私有静态方法名", 调用参数...) ➜ 调用任意类的静态私有方法
*/
@Test
void should_access_private_static_method() {
//调用私有静态方法、无参
assertEquals("static", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFunc"));
//调用私有静态方法、有参
assertEquals("hello + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", "hello", 1));
}
/**
* 4.访问私有静态变量
* PrivateAccessor.getStatic(任意类型, "私有静态字段名") ➜ 读取任意类的静态私有字段
* PrivateAccessor.setStatic(任意类型, "私有静态字段名", 新的值) ➜ 修改任意类的静态私有字段(或静态常量字段)
*/
@Test
void should_access_private_static_field() {
//设置私有的静态变量staticCount的值
PrivateAccessor.setStatic(DemoPrivateAccess.class, "staticCount", 3);
//获取私有的静态变量staticCount的值
assertEquals(Integer.valueOf(3), PrivateAccessor.getStatic(DemoPrivateAccess.class, "staticCount"));
}
/**
* 5.更新final常量
*/
@Test
void should_update_final_field() {
//修改常量pi的值
PrivateAccessor.set(demoPrivateAccess, "pi", 3.14);
//获取常量pi的值
assertEquals(Double.valueOf(3.14), PrivateAccessor.get(demoPrivateAccess, "pi"));
}
/**
* 6. 使用null参数
*/
@Test
void should_use_null_parameter() {
//设置常量pi的值
PrivateAccessor.set(demoPrivateAccess, "pi", null);
//获取常量pi的值
assertNull(PrivateAccessor.get(demoPrivateAccess, "pi"));
assertEquals("null + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", null, 1));
}
}
由于IDE语法报错原因,此特性计划在未来版本中移除,建议采用
PrivateAccessor
方式
第二种方法,除了借助PrivateAccessor
工具类以外,凡是使用了@EnablePrivateAccess
注解的测试类还会被自动赋予以下“特殊能力”:
final
修饰的字段,包括静态常量字段);访问和修改私有、常量成员时,IDE可能会提示语法有误,但编译器将能够正常运行测试。(使用编译期代码增强,目前仅实现了Java
语言的适配)
【a】编写被测试类
跟上述PrivateAccessor
方式一致。
【b】编写测试类
package com.wsh.testable.mock.testablemockdemo;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 演示使用`@EnablePrivateAccess`注解访问私有成员
* 1.直接使用【对象实例.(私有变量/私有方法/常量)】、或者【对象类型.(私有静态变量/私有静态方法)】进行访问
* 2.注意:IDEA会报红,但是运行不报错
* 3.TestableMock默认约定测试类与被测类的包路径相同,且名称为被测类+Test。若测试类名称不符合此约定时,在使用@EnablePrivateAccess注解时,需用srcClass参数显式指明实际的被测类位置。
*/
@EnablePrivateAccess(srcClass = DemoPrivateAccess.class)
class DemoPrivateProcessorTest {
/**
* 被测试类对象
*/
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
@Test
void should_access_private_method() {
List<String> list = new ArrayList<String>() {{
add("a");
add("b");
add("c");
}};
assertEquals("member", demoPrivateAccess.privateFunc());
assertEquals("abc + hello + 1", demoPrivateAccess.privateFuncWithArgs(list, "hello", 1));
}
@Test
void should_access_private_field() {
demoPrivateAccess.count = 2;
assertEquals(Integer.valueOf(2), demoPrivateAccess.count);
}
@Test
void should_access_private_static_method() {
assertEquals("static", DemoPrivateAccess.privateStaticFunc());
assertEquals("hello + 1", DemoPrivateAccess.privateStaticFuncWithArgs("hello", 1));
}
@Test
void should_access_private_static_field() {
DemoPrivateAccess.staticCount = 2;
assertEquals(Integer.valueOf(2), DemoPrivateAccess.staticCount);
}
@Test
void should_update_final_field() {
demoPrivateAccess.pi = 4.13;
assertEquals(Double.valueOf(4.13), demoPrivateAccess.pi);
}
@Test
void should_use_null_parameter() {
demoPrivateAccess.pi = null;
assertNull(demoPrivateAccess.pi);
assertEquals("null + 1", DemoPrivateAccess.privateStaticFuncWithArgs(null, 1));
}
}
可见,直接访问私有成员的方式,就像我们平时调用普通方法和普通成员变量一样使用,由于IDE会报红,后期版本可能会被移除,所以如果编写单元测试时有需要访问被测类的私有属性、方法,推荐使用PrivateAccessor方式。