本文主要是介绍Mock测试详细教程入门这一篇就够了,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
🔥 交流讨论:欢迎加入我们一起学习!
🔥 资源分享:耗时200+小时精选的「软件测试」资料包
🔥 教程推荐:火遍全网的《软件测试》教程
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
1、什么是mock测试

1.png
Mock测试就是在测试活动中,对于某些不容易构造或者不容易获取的比较复杂的数据/场景,用一个虚拟的对象(Mock对象)来创建用于测试的测试方法。
2、为什么要进行Mock测试
Mock是为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在单元测试中,也会出现在集成测试、系统测试过程中。
Mock最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
3、Mock适用场景
1、需要将当前被测单元和其依赖模块独立开来,构造一个独立的测试环境,不关注被测单元的依赖对象,只关注被测单元的功能逻辑。
2、被测单元依赖的模块尚未开发完成A,而被测单元需要依赖模块的返回值进行后续处理。
3、前后端项目中,后端接口开发完成之前,接口联调
4、依赖的上游项目的接口尚未开发完成,需要接口联调测试
5、被测单元依赖的对象较难模拟或者构造比较复杂
如: 支付业务的异常条件很多,但是模拟这种异常条件很复杂或者无法模拟.
4、代码实例
新建测试工程
-
package com.echo.mockito; -
-
public class demo { -
-
//新建一个测试方法 -
public int add(int a, int b){ -
return a + b; -
} -
}
构建mock测试方法
选中测试类,右键选中generate

2.png
点击test

3.png
点击ok后就会在test目录下生成对应的测试方法,和真实的目录是对应的

4.png
5、参数方法说明
@BeforeEach
用在测试前准备,测试前会构建很多的环境配置或者基础配置,都可以在这里设置。
@AfterEach
用于测试后设置。
@Mock
注解可以理解为对 mock 方法的一个替代,不会走真实的方法,模拟真实方法的行为。使用该注解时,要使用MockitoAnnotations.openMocks(this) 方法,让注解生效。
@Spy
1、被Spy的对象会走真实的方法,而mock对象不会,
2、spy方法的参数是对象实例,mock的参数是class。
@InjectMocks
用于将@Mock标记的模拟变量注入到测试类中。
MockitoAnnotations.openMocks(this)
开启mock,配合以上两个注解进行测试。一般放在@BeforeEach 中,在测试前开启,这样不用在每个方法中都的开启了。
Mockito.when(demo.add(1,2)).thenReturn(3):打桩
mock核心,可以设置要测试的方法的结果,这样就会忽略真实方法的执行结果,后续的测试都是基于打桩结果执行。
Mockito.when(demo.add(1,2)).thenThrow(new RuntimeException());
用于模拟异常。
Assertions.assertEquals(3,demo.add(1,2)):断言
测试的主要方式,结果基于此判断。(期望值,实际值)。
6、简单测试
打桩测试 设置方法返回4 ,而实际执行为3 ,测试不通过。

5.png
不打桩测试 因为是spy方式,会走真实的方法 ,测试通过。

6.png
如果是mock方式,如果不打桩会有默认值,测试会不通过,可以试一下。

7.png
7、测试方法说明
详细调用看代码,一般会以下几种:
-
打桩测试
-
异常测试
-
真实方法调用
-
package com.echo.mockito; -
-
import org.junit.jupiter.api.AfterEach; -
import org.junit.jupiter.api.Assertions; -
import org.junit.jupiter.api.BeforeEach; -
import org.junit.jupiter.api.Test; -
import org.mockito.Mock; -
import org.mockito.Mockito; -
import org.mockito.MockitoAnnotations; -
import org.mockito.Spy; -
-
-
class demoTest { -
-
@Mock -
demo demo; -
//测试前开启mock -
@BeforeEach -
void setUp() { -
MockitoAnnotations.openMocks(this); -
} -
-
@Test -
void add() { -
//mock 打桩,就是不管真实的方法如何执行,我们可以自行假设该方法执行的结果 -
//后续的测试都是基于打桩结果来走 -
// Mockito.when(demo.add(1,2)).thenReturn(4); -
// Assertions.assertEquals(3,demo.add(1,2)); -
//当测试方法出现异常,测试方法 如果有try{}catch{} 则可以测试异常是否正常 -
//Mockito.when(demo.add(1,1)).thenThrow(new RuntimeException()); -
//调用真实的方法 -
Mockito.when(demo.add(1,1)).thenCallRealMethod(); -
Assertions.assertEquals(2,demo.add(1,1)); -
} -
-
@AfterEach -
void after(){ -
System.out.println("测试结束"); -
} -
}
![]()
8、Mock静态方法
之前的版本中是不允许模拟测试静态方法的,如果需要测试静态方法,需要替换新的mock依赖,注释掉目前有的依赖,会有冲突。
静态方法测试要用mock的MockedStatic类构建测试方法。
-
<!-- <dependency> -
<groupId>org.mockito</groupId> -
<artifactId>mockito-core</artifactId> -
<version>4.6.1</version> -
</dependency> -
--> -
<dependency> -
<groupId>org.mockito</groupId> -
<artifactId>mockito-inline</artifactId> -
<version>4.3.1</version> -
<scope>test</scope> -
</dependency>
-
package com.echo.mockito.Util; -
-
import org.junit.jupiter.api.Assertions; -
import org.junit.jupiter.api.BeforeEach; -
import org.junit.jupiter.api.Test; -
import org.mockito.MockedStatic; -
import org.mockito.Mockito; -
-
import java.util.Arrays; -
-
import static org.junit.jupiter.api.Assertions.*; -
-
class StaticUtilsTest { -
-
@BeforeEach -
void setUp() { -
} -
-
// 有参静态方法构建 -
@Test -
void range() { -
MockedStatic demo = Mockito.mockStatic(StaticUtils.class); -
//打桩 -
demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12)); -
Assertions.assertTrue(StaticUtils.range(2,6).contains(11)); -
} -
// 无参静态方法构建 -
@Test -
void name() { -
MockedStatic demo = Mockito.mockStatic(StaticUtils.class); -
//打桩 -
demo.when(StaticUtils::name).thenReturn("dhmw"); -
Assertions.assertEquals("dhmw",StaticUtils.name()); -
-
} -
}
![]()
问题:单个的方法执行是没有问题的,但是我们在类上全部执行的时候,发现报错。
提示static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered
意思就是说,每个方法需要有自己的static mock 对象,不允许公用。一起执行的时候,第一个方法占了对象,第二个方法就没有办法再占了。

8.png
解决:每个方法执行完毕后就直接关闭mock对象 demo.close()。相当于是单例的。用完后就的释放,下一个方法才能接着使用。
-
@Test -
void range() { -
MockedStatic demo = Mockito.mockStatic(StaticUtils.class); -
//打桩 -
demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12)); -
Assertions.assertTrue(StaticUtils.range(2,6).contains(11)); -
//关闭 -
demo.close(); -
} -
// 无参静态方法构建 -
@Test -
void name() { -
MockedStatic demo = Mockito.mockStatic(StaticUtils.class); -
//打桩 -
demo.when(StaticUtils::name).thenReturn("dhmw"); -
Assertions.assertEquals("dhmw",StaticUtils.name()); -
//关闭 -
demo.close(); -
}
![]()
9、提升测试覆盖率
案例:数据统计系统,地推人员输入客户的姓名和手机号码,最后构建用户对象存入数据表。
业务代码如下:
-
package com.echo.mockito.service.impl; -
-
import com.echo.mockito.dao.UserDao; -
import com.echo.mockito.service.RegistrationService; -
import com.echo.mockito.vo.User; -
import org.springframework.beans.factory.annotation.Autowired; -
-
import javax.xml.bind.ValidationException; -
import java.sql.SQLException; -
-
public class RegistrationServiceImpl implements RegistrationService { -
@Autowired -
UserDao userDao; -
@Override -
public User register(String name, String phone) throws Exception { -
if (name == null || name.length() == 0){ -
throw new ValidationException("name 不能为空"); -
} -
if (phone == null || phone.length() ==0 ){ -
throw new ValidationException("phone 不能为空"); -
} -
User user; -
try { -
user = userDao.save(name,phone); -
}catch (Exception e){ -
throw new Exception("SqlException thrown" + e.getMessage()); -
} -
return user; -
} -
}
![]()
-
package com.echo.mockito.dao; -
-
import com.echo.mockito.vo.User; -
-
public class UserDao { -
-
public User save(String name,String phnoe){ -
User user = new User(); -
user.setName(name); -
return user; -
} -
}
生成相应的测试代码,此时有几个问题需要思考。
1、测试的类是RegistrationServiceImpl但是里面的userDao如何注入到测试类中?
2、因为需要测试覆盖度,需要考虑该测试类中总共有几种情况需要测试。
(1):两个if抛出了两个异常,总共是2种情况要测试。
(2):保存数据库分为正常保存和异常保存,总共2种情况测试。
综上所述,必须完成这四种情况的测试,才能覆盖所有代码。
相同的方法,我们生成测试用例.代码中有详细的说明,每一种情况都有测试用例。
-
package com.echo.mockito.service.impl; -
-
import com.echo.mockito.dao.UserDao; -
import com.echo.mockito.vo.User; -
import org.junit.jupiter.api.Assertions; -
import org.junit.jupiter.api.BeforeEach; -
import org.junit.jupiter.api.Test; -
import org.mockito.*; -
-
import javax.xml.bind.ValidationException; -
-
import java.sql.SQLException; -
-
import static org.junit.jupiter.api.Assertions.*; -
-
class RegistrationServiceImplTest { -
-
@InjectMocks //RegistrationServiceImpl 实例中注入@Mock标记的类,此处是注入userDao -
@Spy -
private RegistrationServiceImpl registrationService; -
@Mock -
private UserDao userDao; -
@BeforeEach -
void setUp() { -
MockitoAnnotations.openMocks(this); -
} -
-
@Test -
void register() throws Exception { -
// ------------------ 第一种 name 异常情况 测试 start ------------------------ -
String name = null; -
String phone = "1234"; -
try { -
registrationService.register(name,phone); -
}catch (Exception e){ -
Assertions.assertTrue(e instanceof ValidationException); -
} -
-
// ------------------ name 异常情况 测试 end ------------------------ -
-
// ------------------ 第二种 phone 异常情况 测试 start ------------------------ -
name = "111"; -
phone = null; -
try { -
registrationService.register(name,phone); -
}catch (Exception e){ -
Assertions.assertTrue(e instanceof ValidationException); -
} -
// ------------------ phone 异常情况 测试 start ------------------------ -
-
-
// ------------------ 第三种 userDao.save 正常情况 测试 start ------------------------ -
name = "111"; -
phone = "111"; -
//正常保存测试 打桩 走真实的方法 -
Mockito.when(userDao.save(name,phone)).thenCallRealMethod(); -
User user = registrationService.register(name,phone); -
Assertions.assertEquals("111",user.getName()); -
// ------------------ userDao.save 正常情况 测试 end ------------------------ -
-
// ------------------ 第四种 userDao.save 异常情况 测试 start ------------------------ -
//异常保存测试 打桩 通过thenThrow 抛出异常 测试异常是否被捕获 -
Mockito.when(userDao.save(name,phone)).thenThrow(new RuntimeException()); -
try { -
registrationService.register(name,phone); -
}catch (Exception e){ -
Assertions.assertTrue(e instanceof Exception); -
} -
// ------------------ userDao.save 异常情况 测试 end ------------------------ -
} -
}
![]()
如上所示:上面提到的第一个问题,如何注入测试类中的成员变量,是通过@InjectMocks 注解即可完成。
测试覆盖率方法如下:

9.png
测试结果如下:
1、右边部分会显示测试覆盖率。
2、真实代码绿色代表已覆盖测试,红色代表未覆盖测试。

11.png
如果所有的测试情况都100%覆盖,结果如下:

12.png
综上所述就是覆盖测试的方法,总结如下:
1、根据业务代码,分析出所有需要测试的情况。
2、根据不同的测试情况,编写具体的测试代码。
3、针对每一种情况,可以编写具体的测试代码,然后通过打桩,断言等方式,穷尽所有的清册情况即可。
问题:
1、如果真实代码 方法级别有 throws Exception 那么同样的,测试方法也必须方法级别要抛出异常,不然测试会报错。
-
@Test -
void register() throws Exception {
2、保存数据库分为正常和异常,那么先测正常分支,然后在测试异常分支,如果顺序反了,测试先抛出异常,正常的分支就不会执行,这样会导致测试覆盖不全。
最后我邀请你进入我们的【软件测试学习交流群:785128166】, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路
作为一个软件测试的过来人,我想尽自己最大的努力,帮助每一个伙伴都能顺利找到工作。所以我整理了下面这份资源,现在免费分享给大家,有需要的小伙伴可以关注【公众号:程序员二黑】自提!

这篇关于Mock测试详细教程入门这一篇就够了的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!