当前位置: 首页 > 工具软件 > TestableMock > 使用案例 >

阿里开源单元测试工具——Testablemock

施旭东
2023-12-01

TestableMock 阿里开源单元测试Mock工具

开始使用

以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

在测试类添加@EnablePrivateAccess注解,可以在测试类中直接使用被测类的私有成员变量和方法

@Test
void testCallInner(){

    // 访问私有变量
    String var = demoMock.variable;
    // 私有变量赋值
    demoMock.variable = "mock_variable";

    // 访问私有方法
    String var1 = demoMock.innerFunc();
    // 访问带参数的私有方法
    String var2 = demoMock.innerFunc("OK");

}

使用中IDE可能会提示语法错误,但编译器可以正常运行

PrivateAccessor

若不希望看到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");

}

快速Mock任意方法

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方法的首位参数即为目标方法所属类型,参数名称随意。通常为了代码便于阅读,建议将此参数统一命名为selfsrc。举例如下:

// 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和普通方法相同

覆写任意类的new操作

在测试类里定义一个返回值类型为要被创建的对象类型,且方法参数与要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");
}

原文档地址 GitHub

 类似资料: