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

TestableMock使用教程

郤浩慨
2023-12-01

前言

TestableMock是阿里的单元测试工具,支持Mock公共方法、私有方法、静态方法、构造方法等功能,使用方便快捷
目前主要的Mock工具主要有Mockito、Spock、PowerMock和JMockit,基本差异如下:

工具原理最小Mock单元对被Mock方法的限制上手难度IDE支持
Mockito动态代理不能Mock私有/静态和构造方法较容易很好
Spock动态代理不能Mock私有/静态和构造方法较复杂一般
PowerMock自定义类加载器任何方法皆可较复杂较好
JMockit运行时字节码修改不能Mock构造方法(new操作符)较复杂一般
TestableMock运行时字节码修改方法任何方法皆可很容易一般

一、依赖

<!-- 统一配置版本 -->
<properties>
    <testable.version>0.7.2</testable.version>
</properties>
<!-- 添加依赖 -->
<dependencies>
    <dependency>
        <groupId>com.alibaba.testable</groupId>
        <artifactId>testable-all</artifactId>
        <version>${testable.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<!-- 添加插件配置 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
            </configuration>
        </plugin>
    </plugins>
</build>

官网文档:https://alibaba.github.io/testable-mock/#/

二、示例

创建Mock容器,共有三方法

  • 注解:在任意包路径下创建一个类,类名随意,在测试类中使用注解声明Mock类
  • 内部类:在测试类中创建一个静态内部类,类名必须为Mock
  • 独立类:在测试类相同包路径下创建一个类,类名为被测试类名+Mock

优先级:注解->内部类->独立类

内部类

// 测试类
class TestServiceTest {
    
    // Mock容器
    public static class Mock {

        @MockInvoke(targetClass = TestService.class)
        private String privateMethod(String str) {
            return "456";
        }
    }
    
    // 测试方法
    @ParameterizedTest
    @CsvSource({ "123", "456" })
    void testPublicMethod(String str) {
        String res = testService.publicMethod(str);
        assertEquals("456", res);
    }
    // 正常调用返回结果应为123、456
    // 经过Mock返回结果两次都为456
}

// 被测试类
public class TestService {
    
     public String publicMethod(String  str) {
        return privateMethod(str);
    }
    
     private String privateMethod(String str) {
        return str;
    }
}

注解

// 独立Mock类
public class ServiceMock {
    
    @MockInvoke(targetClass = TestService.class)
    private String privateMethod(String str) {
        return "456";
    }
    // 其他Mock方法
}

// 测试类
// 使用注解声明使用的Mock类
@MockWith(ServiceMock.class)
class TestServiceTest {
    // 测试代码
}

独立类

// 独立Mock类
public class TestServiceMock {
    
    @MockInvoke(targetClass = TestService.class)
    private String privateMethod(String str) {
        return "456";
    }
    // 其他Mock方法
}

// 测试类
class TestServiceTest {
    // 测试代码
}

// 被测试类
class TestService{}

三、其他用法

1、复用

如果有一些方法需要经常被Mock,不需要重复写Mock方法,只需要创建一个独立的Mock类,在测试类中可以使用注解或继承使用这些类,使用方法如下:

注解:

使用注解的方法主参照示例中的使用独立类Mock方法

继承:

class TestServiceTest {
    // 内部Mock类继承独立的Mock类
    public static class Mock extends ServiceMock {
        // 自定义Mock方法
    }
}

如果继承的类与内部类中有相同的Mock方法,该Mock方法会被重写

2、校验

在测试中有时会需要校验方法调用参数是否正确,方法调用次数是否正确的情况

TestableMock中提供了校验器(verifier)和匹配器(matcher)来实现这一功能

基本校验器

  • with(Object… args) → 验证方法是否被指定参数调用过

  • withInOrder(Object… args) → 如果指定方法被调用了多次,依据实际调用顺序依次匹配

  • withTimes(int expectedCount) → 验证方法是否被调用过指定次数,忽略对调用参数的检查

  • without(Object… args) → 验证方法从未被使用指定参数调用过

  • times(int count) → 连在with()或withInOrder()方法之后使用,验证该方法被同样条件的参数调用过了指定次数

基本匹配器

  • any() → 匹配任何值,包括Null
  • anyString() → 匹配任何字符串
  • anyNumber() → 匹配任何数值(整数或浮点数)

使用示例:

 InvocationVerifier.verifyInvoked("getJSONArray").with(InvocationMatcher.anyString());

更多方法:https://alibaba.github.io/testable-mock/#/zh-cn/doc/invoke-matcher

3、测试私有方法

部分私有方法通过公共方法间接去调用达不到较高的覆盖率,TestableMock支持直接测试私有方法

PrivateAccessor工具类可以直接访问私有方法,

String res = PrivateAccessor.invoke(new TestService(), "privateMethod", "123");

更多方法:https://alibaba.github.io/testable-mock/#/zh-cn/doc/private-accessor

4、构造数据

public class Article {
    private Integer id;

    private String name;

    private String url;

    private Date createDate;
}
// 构造数据
Article newInstance = OmniConstructor.newInstance(Article.class);
// 构造结果
// Article(id=0, name=, url=, createDate=Wed Jan 26 09:42:31 CST 2022)

构造的数据均为基本类型默认值,如Integer类型数据均为0,String类型数据均为“”,时间类型数据为当前时间

文档地址:https://alibaba.github.io/testable-mock/#/zh-cn/doc/omni-constructor

5、在Mock方法中区分调用来源

在一个测试类中,可能会有同一个Mock方法在不同场景希望返回不同的结果的需求,TestableMock可以通过TestableTool.SOURCE_METHOD变量识别进入该Mock方法的的被测试方法

// Mock方法
 @MockInvoke(targetClass = TestService.class)
private String privateMethod(String str) {
    return TestableTool.SOURCE_METHOD;
}

// 测试方法
@Test
void testPublicMethodV1() {
    String res = testService.publicMethod("str");
    assertEquals("publicMethod", res);
}

@Test
void testPublicMethodV2() {
    String res = testService.publicMethodV2("str");
    assertEquals("publicMethodV2", res);
}

// 被测试方法
public String publicMethod(String  str) {
    return privateMethod(str);
}
public String publicMethodV2(String str) {
    return privateMethod(str);
}

public List<Article> getAllArticle(){
    ArticleExample articleExample = new ArticleExample();
    articleExample.createCriteria().andIdIsNotNull();
    return articleMapper.selectByExample(articleExample);
}

还可以通过TestableTool.MOCK_CONTEXT变量为Mock方法注入“额外的上下文参数”,从而区分处理不同的调用场景。使用示例:

// Mock方法
@MockInvoke(targetClass = TestService.class)
private String privateMethod(String str) {
    // 通过MOCK_CONTEXT的值判断需要返回的结果
    if("v1".equals(TestableTool.MOCK_CONTEXT.get("public"))) {
        return "456";
    }
    return "789";
}

// 测试方法,为MOCK_CONTEXT变量赋值
@Test
void testPublicMethodV1() {
    TestableTool.MOCK_CONTEXT.put("public", "v1");
    String res = testService.publicMethod("str");
    assertEquals("456", res);
    TestableTool.MOCK_CONTEXT.put("public", "v2");
    res = testService.publicMethod("str");
    assertEquals("789", res);
}

四、注意事项

1、Eclipse启动配置

由于Eclipse内置的单元测试执行器完全忽略pom.xml文件的配置,因此若需使用Mock功能,需进行额外配置。

官网文档地址:https://alibaba.github.io/testable-mock/#/zh-cn/doc/use-in-ide

右键->run->Run Configurations…->Arguments->VM arguments

// 在运行配置中将以下参数加入
-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar
// 需要替换为自己的maven仓库地址与依赖版本,替换结果示例:
-javaagent:D:\workspace\Maven\repository/com/alibaba/testable/testable-agent/0.7.2/testable-agent-0.7.2.jar

2、更新

TestableMock从0.6.X更新到0.7.X以后变更了一些注解的名称:@MockMethod和@MockConstructor分别更名为@MockInvoke和@MockNew。


 欢迎前往博客主页查看更多内容

 如果觉得不错,期待您的点赞、收藏、评论、关注

 ​ 如有错误欢迎指正!

 类似资料: