以maven为例,在pom.xml中添加配置
<properties>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.11</testable.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<version>3.0.0-M5</version>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
</plugins>
</build>
被测类
public class DemoMock {
private String variable = "variable";
public ServiceOne createServiceOne(String field) {
return new ServiceOne(field);
}
public String callInnerFunc() {
return innerFunc();
}
public String callInnerFunc(String arg) {
return innerFunc(arg);
}
public String callCommonFunc() {
return commonFunc();
}
public String callCommonFunc(String arg) {
return commonFunc(arg);
}
public String commonFunc() {
return "_COMMON_FUNC";
}
public String commonFunc(String arg) {
return "_COMMON_FUNC" + "-" + arg;
}
private String innerFunc() {
return "_INNER_FUNC";
}
private String innerFunc(String arg) {
return "_INNER_FUNC" + "-" + arg;
}
}
测试类
@EnablePrivateAccess
public class DemoMockTest {
DemoMock demoMock = new DemoMock();
@MockMethod(targetClass = DemoMock.class)
private String innerFunc() {
return "_MOCK_FUNC";
}
@MockMethod(targetClass = DemoMock.class, targetMethod = "innerFunc")
private String innerFuncMock() {
return "_MOCK_FUNC";
}
@MockMethod(targetClass = DemoMock.class)
private String innerFunc(String arg) {
return "_MOCK_FUNC" + "_" + arg;
}
@MockMethod(targetClass = DemoMock.class)
private String commonFunc() {
return "_MOCK_COMMON";
}
@MockMethod(targetClass = DemoMock.class)
private String commonFunc(String arg) {
return "_MOCK_COMMON" + "_" + arg;
}
@MockConstructor
private ServiceOne create(String arg) {
return new ServiceOne("_MOCK_" + arg);
}
@Test
void testCallInner(){
// 覆写`new`操作
ServiceOne serviceOne = demoMock.createServiceOne("_WITH_ARG");
// 访问私有变量
// String var = demoMock.variable;
PrivateAccessor.get(demoMock, "variable");
// 私有变量赋值
// demoMock.variable = "mock_variable";
PrivateAccessor.set(demoMock, "variable", "mock_variable");
// 访问私有方法
// String var1 = demoMock.innerFunc();
PrivateAccessor.invoke(demoMock, "innerFunc");
// 访问带参数的私有方法
// String var2 = demoMock.innerFunc("OK");
PrivateAccessor.invoke(demoMock, "innerFunc", "OK");
// mock方法调用
Assertions.assertEquals("_MOCK_FUNC", demoMock.callInnerFunc());
Assertions.assertEquals("_MOCK_FUNC_OK", demoMock.callInnerFunc("OK"));
Assertions.assertEquals("_MOCK_COMMON", demoMock.callCommonFunc());
Assertions.assertEquals("_MOCK_COMMON_OK", demoMock.callCommonFunc("OK"));
}
}
testablemock 可以实现对被测类的私有成员直接访问,以及变量赋值
在测试类添加
@EnablePrivateAccess
注解,可以在测试类中直接使用被测类的私有成员变量和方法
@Test
void testCallInner(){
// 访问私有变量
String var = demoMock.variable;
// 私有变量赋值
demoMock.variable = "mock_variable";
// 访问私有方法
String var1 = demoMock.innerFunc();
// 访问带参数的私有方法
String var2 = demoMock.innerFunc("OK");
}
使用中IDE可能会提示语法错误,但编译器可以正常运行
若不希望看到IDE的语法错误提醒,也可以借助PrivateAccessor
工具类来直接访问私有成员。
这个类提供了6个静态方法
PrivateAccessor.get(被测对象, "私有字段名")
➜ 读取被测类的私有字段PrivateAccessor.set(被测对象, "私有字段名", 新的值)
➜ 读取被测类的私有字段PrivateAccessor.invoke(被测对象, "私有方法名", 调用参数..
)` ➜ 调用被测类的私有方法PrivateAccessor.getStatic(被测类型, "私有静态字段名")
➜ 读取被测类的静态私有字段PrivateAccessor.setStatic(被测类型, "私有静态字段名", 新的值)
➜ 修改被测类的静态私有字段(或静态常量字段)PrivateAccessor.invokeStatic(被测类型, "私有静态方法名", 调用参数..)
➜ 调用被测类的静态私有方法@Test
void testCallInner(){
// 访问私有变量
PrivateAccessor.get(demoMock, "variable");
// 私有变量赋值
PrivateAccessor.set(demoMock, "variable", "mock_variable");
// 访问私有方法
PrivateAccessor.invoke(demoMock, "innerFunc");
// 访问带参数的私有方法
PrivateAccessor.invoke(demoMock, "innerFunc", "OK");
}
TestableMock 允许用户直接定义需要Mock的单个方法,并遵循约定优于配置的原则,按照规则自动在测试运行的时替换被测方法中的指定方法调用。
归纳起来就两条
- Mock非构造方法,拷贝原方法定义到测试类,如
@MockMethod
注解- Mock构造方法,拷贝原方法定义到测试类,返回值换成构造的类型,方法名随意,如
@MockContructor
注解
Mock约定
- 测试类与被测类的包路径应相同,且名称为
被测类+Test
。
当前Mock方法(即包含
@MockMethod
或@MockContructor
注解的方法)会在运行期被自动修改为static
方法,请勿在Mock方法的定义中访问任何非静态成员。当Mock方法内容较复杂(包含Lambda语句、构造块、匿名类等)时,编译器会在构建期生成额外的非静态临时方法,导致"Bad type in operand stack"错误。如果有遇到此类错误,请将Mock方法显式加上static
修饰即可解决。这个问题会在0.5
版本中彻底解决。
在测试类里定义一个又@MockMethod
注解的普通方法,使它与需被覆写的方法名称、参数、返回值类型完全一致,并在注解的targetClass
参数指定该方法原本所属对象类型。
此时被测类中对改需被覆写方法的调用,将在单元测试运行时,自动被替换成对上述自定义方法的调用。
例如,在被测类中有对commonFunc()
的调用,返回值类型是String
,如要定义不同返回值进行测试,只需在测试类定义如下方法
@MockMethod(targetClass = String.class)
private String trim() {
return "_MOCK_TRIM";
}
当遇到待覆写方法有重名时,可以将需覆写的方法名写到@MockMethod
注解的targetMethod
参数里,这样Mock方法自身就可以自由命名了
上边的例子可以修改成:
// 使用`targetMethod`指定需Mock的方法名
// 方法名称随意命名,方法参数和返回值需遵循相同的匹配规则
@MockMethod(targetClass = String.class, targetMethod = "trim")
private String trimMock() {
return "_MOCK_TRIM";
}
TestableMock
约定,当@MockMethod
注解的targetClass
参数为空时,Mock方法的首位参数即为目标方法所属类型,参数名称随意。通常为了代码便于阅读,建议将此参数统一命名为self
或src
。举例如下:
// Mock方法在参数列表首位添加一个类型为`String`的参数(名字随意)
// 此参数可用于获得当时的实际调用者的值和上下文
@MockMethod
private String term(String self) {
// 可以直接调用原方法,此时Mock方法仅用于记录调用,常见于对void方法的测试
return self.term();
}
做法与上边一样,只需把targetClass
参数赋值为被测类,即可实现对被测类自身成员方法(不论是公有还是私有)的覆写
例如:
// 被测类型是DemoMock
@MockMethod(targetClass = DemoMock.class)
private String commonFunc() {
return "_MOCK_COMMON";
}
对于静态方法的Mock和普通方法相同
在测试类里定义一个返回值类型为要被创建的对象类型,且方法参数与要Mock的构造函数参数完全一致的方法,名称随意,然后加上@MockContructor
注解。
此时被测类所有用new
创建指定类(使用了与Mock方法参数一致的构造函数)的操作,将被替换为对该自定义方法的调用。
例如被测类中有new ServiceOne('something')
的调用,希望使用测试参数创建临时对象,只需定义如下Mock方法:
@MockConstructor
private ServiceOne create(String arg) {
return new ServiceOne("_MOCK_" + arg);
}
@Test
void testCallInner(){
// serviceOne{field = "_MOCK_WITH_ARG"}
ServiceOne serviceOne = demoMock.createServiceOne("_WITH_ARG");
}