Mockito和JMockit的对比:https://www.sibida.vip/article/126324
Mockito、JMockit、EasyMock、PowerMock、jMock、Unitils对比 :
简介
jmockit用来写ut,即单元测试。
中文官网:http://jmockit.cn/showChannel.htm?channel=1
关于单元测试ut、功能测试ft、系统测试st、验收测试uat:
关于官网没有提到的,个人的经验:
- 提示期待和被期待的对象不同,但对比结果相同,如果不是必须同一个实例,可能是"null"在控制台打印的是null,null在控制台也打印的是null,可能是1L和1都显示1,使用BeyondCompare等对比工具发现不了。
- 控制台打印的被期待的逻辑是测试中写的,实际的逻辑是被测试的代码。
- @Mocked用在ut的变量里。
- Spring Test中提供了一些模拟的对象可以方便地使用。如:MockHttpServletRequest、MockHttpServletResponse、MockMultipartFile、MockFilterChain
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
- ut类中被注入的成员变量,也就是加了@Injectable的成员变量,是被jmockit自己实现,个人修改后,除非通过测试方法传参进去,否则不会生效。不会传参进去,又想自定义这个成员变量,只能自己写一个实现类,然后用jmockit提供的FieldReflection.setFieldValue(field, targetObject, value),它会通过反射将ut执行时的被测试类的成员变量改了,mocked后赋值、injectable后赋值、用mockup都不能解决问题。而且必须在测试方法执行前,不能写在Junit的@Before里,此时@Tested的变量还没有实例化;field可以被测试类.class.getDeclaredField(变量名)得到,targetObject就是@Tested的变量,value就是自己写的被测试类的成员变量的实现(接口实现或子类)。
- 被测试类里用到了自己,也就是注入了自己,此时,被测试方法内部用了自己,就会有NPE,也只能通过FieldReflection.setFieldValue解决。
- 被测试类里@Autowired方式注入或者构造方法的方法注入,都用@Injectable。
- coverage插件已经安装,打开工程后没有coverage窗口,coverage运行按钮是灰色的,重新打开工程可以解决。
- 在new Expectations里用jmockit的withInstanceLike方法,传入的参数不需要内容完全相同,也可以继续向下执行,可以结合将实例赋值来解决mapper执行insert后得到自增ID的场景。withInstanceOf只需传入参数类型就行。
- 将实例赋值,传递下去,实例对应的类不能是序列化的类:
XXX xxx = new XXX();
new Expectations(XXX.class){
{
new XXX();
result = xxx;
}
};
- 解决TimeUtils:
new Expectations(TimeUtils.class){
{
TimeUtils.currentTimeSecs();
result = 1;
}
};
- System的currentTimeMillis是native方法,不能像TimeUtils一样解决,需用MockUp,MockUp对当前所在方法内涉及到的所有实例生效:
new MockUp<System>() {
@Mock
public long currentTimeMillis() {
return 1;
}
};
- BufferedWriter、FileWriter等涉及到文件,也可以用MockUp解决,因为文件可能在服务器上,ut不依赖外部资源,所以不想报文件找不到,不想读写文件;
clinit可以模拟静态代码块http://jmockit.cn/showChannel.htm?channel=4:
new MockUp<BufferedWriter>() {
@Mock
public void close() {
}
@Mock
public void flush() {
}
};
new MockUp<FileWriter>() {
@Mock
public void $init(File file, boolean append) {
}
@Mock
public void close() {
}
};
new MockUp<FileUtils>() {
@Mock
public void writeByteArrayToFile(File file, byte[] data) {
}
};
- 涉及到多线程的测试,模拟的方法被执行的次序需要固定,否则Expectation不能执行成功,所以需要将被测试类的多线程模拟成单线程执行:
List<Runnable> runnableList = new ArrayList<>();
new MockUp<ThreadPoolExecutor>(){
@Mock
public Future<?> submit(Runnable task){
runnableList.add(task);
// 2是任务的个数,将任务都存起来,存完后再依次执行,如果模拟时线程个数是外部传入的,可以直接传入1,多线程改单线程
if(runnableList.size() == 2){
runnableList.forEach(Runnable::run);
}
return null;
}
};
- 如果MockUp使用时,报mockit.internal.ClassFile$NotFoundException: Unable to find class file for,需调整MockUp的使用顺序,如:
new MockUp<URLClassLoader>() {
@Mock
public InputStream getResourceAsStream(String name) {
return null;
}
};
new MockUp<FileUtils>(){
@Mock
public void writeByteArrayToFile(File file, byte[] data){
}
};
// 会报:mockit.internal.ClassFile$NotFoundException: Unable to find class file for,交换一下顺序就能解决
- 一个类的静态成员变量实例化时抛出异常,想不抛出,可以用FieldReflection.setFieldValue,第二个参数传null就行。
- 被测试类的私有方法不被公开的调用,还想测试一下,可以用jmockit提供的MethodReflection.invoke反射执行。
其它:
18. IDEA的被测试类中,Mac是Command+Shift+T,Windows是Ctrl+Shift+T,然后回车,可以看到已有的测试类,也可以生成新的测试类:选择生成策略。
19. 在目录上可以右键Run … with coverage可以查看类、方法、行的覆盖率。
20. 运行ut后,就可以编辑下一个ut方法了,毕竟ut执行前jmockit需要执行一些代码。 开始运行后,编辑不会影响运行,可以节省ut编写时间,不要问我怎么知道的,当要覆盖37000行时,节省出来的时间很重要。
21. IDEA的Settings里搜Coverage,勾选Add to the active suits可以将目前的ut执行的Coverage合并到上一次执行的Coverage里,可以实时了解当前大目录的Coverage。
22. 可以用Statistic插件先看一下代码行集中在哪些目录,哪些类,可以更快地提升行覆盖率。
23. 被测试类的方法声明改测试类的同一个方法声明,IDEA里用正则替换:
public\s.\s(.)(.*)\s{0,1}{
public void $1() {
24. 测试类的方法统一test开头,下一个字母大写,IDEA里用正则替换:
@Test
public void ([a-su-z])
@Test
public void test\u$1
25. 如果被测试的类实例化时报错,可以在静态代码块中给被测试类的实例传入正确的参数。
补充:
jmockit教程_jmockit使用总结-MockUp重点介绍:
Java服务端单元测试指南: