fluent mock是一款利用instrument(java agent)和 annotation processor这2个java提供的黑科技实现测试过程 中mock Java类/接口/对象的Mock工具。
fluent mock和主流的mock框架对比有什么不同,及存在的必要?
问题一:和市面上其它mock框架对比
市面上已经有各种形形色色的mock框架,知名的就有easy-mock、 jmock、 mockito、 power-mock、 jmockit, 各有绝招,为什么还要有fluent-mock呢?
首先大概直观的看下各色mock框架的招式和优缺点
框架 | 原理 | mock对象 | mock范围 | Jacoco |
---|---|---|---|---|
Mockito | 动态代理 | 对象,需先创建mock对象,然后对mock对象指定行为 | 不能Mock私有/静态和构造方法 | 兼容 |
PowerMock | 类加载器 | 对象,需先创建mock对象,然后对mock对象指定行为 | 任何方法 | 不兼容 |
EasyMock | 动态代理 | 对象,需先创建mock对象,然后对mock对象指定行为 | 不能Mock私有/静态和构造方法 | 兼容 |
jMock | 动态代理 | 对象,需先创建mock对象,然后对mock对象指定行为 | 不能Mock私有/静态和构造方法 | 兼容 |
jMockit | instrument | 类,接口。修改类字节码,无需先创建mock对象,对所有该类实例生效 | 任何方法(高版本限制了对private方法的mock) | 兼容 |
Fluent-Mock | instrument | 类,接口,对象。修改类字节码,无需先创建mock对象,可以对所有该类所有实例生效,也可以对指定的实例才生效。 | 除native限定外的任何方法 | 兼容 |
从技术体系上看,基本可以分成2类
- 使用动态代理(或classloader)
主要是传统的mock框架: mockito, powermock, easymock, jmock等,因为技术特点的限制,
对mock行为不能随心所欲,主要限制有
- 不能对私有函数,final类,final函数mock外;
- 不能对构造函数,静态代码块进行mock
- 需要使用mock对象替换掉原来已经存在的对象,需要管理对象的依赖关系。对从原来类new出来的对象实例,没法指定行为。
- 修改原类的字节码 jmockit和fluent-mock, 因为是修改既有类的代码逻辑,克服了动态代理方式的限制,任意对象进入要mock的方法, 执行的都是修改过的字节码逻辑,无需刻意维护(替换)原来的依赖关系。
问题二: 和jmockit框架对比
既然fluent-mock和jmockit采用的是同一个技术体系,jmockit也已经发展多年了,fluent-mock有什么特别的地方呢? 作者也是多年的jmockit的重度使用者,因为jmockit相对其它mock框架,来的直接和方便,特别是new MockUP和@Mock方式来进行mock。 多年的使用,觉的jmockit还是不尽如意,主要表现在:
- Expectations的语法比较鸡肋, 可能看法不一样,个人早期有使用,后面基本放弃这种用法。
- new MockUp的方式,虽然可以mock所有方法,但如果原有方法名称和参数做了重构,编译上没法发现。
- 早期的版本可以mock私有方法,后期版本把private方法mock给屏蔽了。
- new MockUp方式对所有给类的实例生效,没法做到只对指定实例生效(Expectations方式可以),但对其它实例无影响。
- 在指定mock行为的语法,较其它mock框架在流畅度上有点差距。
fluent-mock主要是致力于解决上述不足,吸收了jmockit框架的new MockUp + @Mock的特性,并且根据要mock的类元信息,动态编译生成mock行为指导方法。 充分利用fluent接口的特点,可以无记忆流畅的指定mock行为。 不仅可以普适性的mock,还可以针对性(对指定对象)mock。
来段代码,简单感受下fluent-mock的特性:
/**
* 对MyServiceImpl这个类进行mock
*/
@Mocks({MyServiceImpl.class})
public class MyServiceImplTest {
/** 这个类是AnnotationProcessor生成的, 可以给使用者提供fluent语法指导 **/
private MyServiceImplTestMocks mocks = new MyServiceImplTestMocks();
@DisplayName("验证fluent mock对指定实例进行mock")
@Test
public void test() {
/** 无差别创建3个实例**/
MyServiceImpl myService1 = new MyServiceImpl();
MyServiceImpl myService2 = new MyServiceImpl();
MyServiceImpl myService3 = new MyServiceImpl();
/** 只对实例1和3进行行为指定, 放过实例2 **/
mocks.MyServiceImpl(myService1).sayHello2.thenReturn("1");
mocks.MyServiceImpl(myService3).sayHello2.thenReturn("3");
String result1 = myService1.sayHello("m");
String result2 = myService2.sayHello("m");
String result3 = myService3.sayHello("m");
/** 验证接口行为 **/
Assertions.assertEquals("1", result1);
Assertions.assertEquals("hello:m", result2);
Assertions.assertEquals("3", result3);
}
}
public class MyServiceImpl {
public String sayHello(String name) {
return "hello:" + name;
}
}
fluent mock配置
maven配置
- jar包引用 在项目pom.xml文件中
<project>
<properties>
<fluent-mock.version>1.0.4</fluent-mock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.test4j</groupId>
<artifactId>fluent-mock</artifactId>
<version>${fluent-mock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 和surefire 和maven-surefire-plugin插件一起使用,需要在 argLine 参数配置参数
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:${settings.localRepository}/org/test4j/fluent-mock/${fluent-mock.version}/fluent-mock-${fluent-mock.version}.jar</argLine>
</configuration>
</plugin>
</plugins>
</build>
- 和surefire + jacoco一起 如果同时使用了jacoco的来统计单元测试覆盖率,则需在配置中添加一个@{argLine}
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/org/test4j/fluent-mock/${fluent-mock.version}/fluent-mock-${fluent-mock.version}.jar</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
gradle配置
dependencies {
testCompile('org.test4j:fluent-mock:${fluent-mock.version}')
//annotationProcessor('org.test4j:fluent-mock:${fluent-mock.version}')
testAnnotationProcessor('org.test4j:fluent-mock:${fluent-mock.version}')
test {
jvmArgs "-javaagent:${classpath.find { it.name.contains("fluent-mock") }.absolutePath}"
useJUnitPlatform()
}
}
IDE配置
大部分情况下,IDE会自动识别maven和gradle的配置,如果mock框架没有起作用,可以在运行测试的jvm参数中加上:
-javaagent:{你本地仓库根路径}/org/test4j/fluent-mock/1.0.4/fluent-mock-1.0.4.jar