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容器,共有三方法
优先级:注解->内部类->独立类
内部类
// 测试类
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{}
如果有一些方法需要经常被Mock,不需要重复写Mock方法,只需要创建一个独立的Mock类,在测试类中可以使用注解或继承使用这些类,使用方法如下:
注解:
使用注解的方法主参照示例中的使用独立类Mock方法
继承:
class TestServiceTest {
// 内部Mock类继承独立的Mock类
public static class Mock extends ServiceMock {
// 自定义Mock方法
}
}
如果继承的类与内部类中有相同的Mock方法,该Mock方法会被重写
在测试中有时会需要校验方法调用参数是否正确,方法调用次数是否正确的情况
TestableMock中提供了校验器(verifier)和匹配器(matcher)来实现这一功能
基本校验器
with(Object… args) → 验证方法是否被指定参数调用过
withInOrder(Object… args) → 如果指定方法被调用了多次,依据实际调用顺序依次匹配
withTimes(int expectedCount) → 验证方法是否被调用过指定次数,忽略对调用参数的检查
without(Object… args) → 验证方法从未被使用指定参数调用过
times(int count) → 连在with()或withInOrder()方法之后使用,验证该方法被同样条件的参数调用过了指定次数
基本匹配器
使用示例:
InvocationVerifier.verifyInvoked("getJSONArray").with(InvocationMatcher.anyString());
更多方法:https://alibaba.github.io/testable-mock/#/zh-cn/doc/invoke-matcher
部分私有方法通过公共方法间接去调用达不到较高的覆盖率,TestableMock支持直接测试私有方法
PrivateAccessor工具类可以直接访问私有方法,
String res = PrivateAccessor.invoke(new TestService(), "privateMethod", "123");
更多方法:https://alibaba.github.io/testable-mock/#/zh-cn/doc/private-accessor
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
在一个测试类中,可能会有同一个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);
}
由于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
TestableMock从0.6.X更新到0.7.X以后变更了一些注解的名称:@MockMethod和@MockConstructor分别更名为@MockInvoke和@MockNew。
欢迎前往博客主页查看更多内容
如果觉得不错,期待您的点赞、收藏、评论、关注
如有错误欢迎指正!