0


mock详细教程入门这一篇就够了

1、什么是mock测试

  1. Mock 测试就是在测试活动中,对于某些不容易构造或者不容易获取的比较复杂的数据/场景,用一个虚拟的对象(Mock对象)来创建用于测试的测试方法。

2、为什么要进行Mock测试

  1. Mock是为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在单元测试中,也会出现在集成测试、系统测试过程中。
  2. Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。

3、Mock适用场景

  1. 需要将当前被测单元和其依赖模块独立开来,构造一个独立的测试环境,不关注被测单元的依赖对象,只关注被测单元的功能逻辑。

  2. 被测单元依赖的模块尚未开发完成,而被测单元需要依赖模块的返回值进行后续处理。

3、前后端项目中,后端接口开发完成之前,接口联调

4、 依赖的上游项目的接口尚未开发完成,需要接口联调测试

5、被测单元依赖的对象较难模拟或者构造比较复杂

如: 支付业务的异常条件很多,但是模拟这种异常条件很复杂或者无法模拟.

4、代码实例

  1. ** 1、新建测试工程**
  1. package com.echo.mockito;
  2. public class demo {
  3. //新建一个测试方法
  4. public int add(int a, int b){
  5. return a + b;
  6. }
  7. }

2、构建mock测试方法

** (1)、选中测试类,右键选中generate
(2)、点击test**

** (2)、点击ok后就会在test目录下生成对应的测试方法,和真实的目录是对应的**

5、参数方法说明

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

6、简单测试

** 1、打桩测试 设置方法返回4 ,而实际执行为3 ,测试不通过。**

  1. ![](https://img-blog.csdnimg.cn/dc8cdf58be364c189ab5cb4bee3a7872.png)

** 2、不打桩测试 因为是spy方式,会走真实的方法 ,测试通过。**

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

7、测试方法说明

详细调用看代码,一般会以下几种:

  1. 1、打桩测试
  2. 2、异常测试
  3. 3、真实方法调用
  1. package com.echo.mockito;
  2. import org.junit.jupiter.api.AfterEach;
  3. import org.junit.jupiter.api.Assertions;
  4. import org.junit.jupiter.api.BeforeEach;
  5. import org.junit.jupiter.api.Test;
  6. import org.mockito.Mock;
  7. import org.mockito.Mockito;
  8. import org.mockito.MockitoAnnotations;
  9. import org.mockito.Spy;
  10. class demoTest {
  11. @Mock
  12. demo demo;
  13. //测试前开启mock
  14. @BeforeEach
  15. void setUp() {
  16. MockitoAnnotations.openMocks(this);
  17. }
  18. @Test
  19. void add() {
  20. //mock 打桩,就是不管真实的方法如何执行,我们可以自行假设该方法执行的结果
  21. //后续的测试都是基于打桩结果来走
  22. // Mockito.when(demo.add(1,2)).thenReturn(4);
  23. // Assertions.assertEquals(3,demo.add(1,2));
  24. //当测试方法出现异常,测试方法 如果有try{}catch{} 则可以测试异常是否正常
  25. //Mockito.when(demo.add(1,1)).thenThrow(new RuntimeException());
  26. //调用真实的方法
  27. Mockito.when(demo.add(1,1)).thenCallRealMethod();
  28. Assertions.assertEquals(2,demo.add(1,1));
  29. }
  30. @AfterEach
  31. void after(){
  32. System.out.println("测试结束");
  33. }
  34. }

8、Mock静态方法

  1. 1、之前的版本中是不允许模拟测试静态方法的,如果需要测试静态方法,需要替换新的mock依赖,注释掉目前有的依赖,会有冲突。
  2. 2、静态方法测试要用mockMockedStatic类构建测试方法。
  1. <!-- <dependency>
  2. <groupId>org.mockito</groupId>
  3. <artifactId>mockito-core</artifactId>
  4. <version>4.6.1</version>
  5. </dependency>
  6. -->
  7. <dependency>
  8. <groupId>org.mockito</groupId>
  9. <artifactId>mockito-inline</artifactId>
  10. <version>4.3.1</version>
  11. <scope>test</scope>
  12. </dependency>
  1. package com.echo.mockito.Util;
  2. import org.junit.jupiter.api.Assertions;
  3. import org.junit.jupiter.api.BeforeEach;
  4. import org.junit.jupiter.api.Test;
  5. import org.mockito.MockedStatic;
  6. import org.mockito.Mockito;
  7. import java.util.Arrays;
  8. import static org.junit.jupiter.api.Assertions.*;
  9. class StaticUtilsTest {
  10. @BeforeEach
  11. void setUp() {
  12. }
  13. // 有参静态方法构建
  14. @Test
  15. void range() {
  16. MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
  17. //打桩
  18. demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));
  19. Assertions.assertTrue(StaticUtils.range(2,6).contains(11));
  20. }
  21. // 无参静态方法构建
  22. @Test
  23. void name() {
  24. MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
  25. //打桩
  26. demo.when(StaticUtils::name).thenReturn("dhmw");
  27. Assertions.assertEquals("dhmw",StaticUtils.name());
  28. }
  29. }

** 问题:单个的方法执行是没有问题的,但是我们在类上全部执行的时候,发现报错。**

提示static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered

意思就是说,每个方法需要有自己的static mock 对象,不允许公用。一起执行的时候,第一个方法占了对象,第二个方法就没有办法再占了。

解决:每个方法执行完毕后就直接关闭mock对象 demo.close()。相当于是单例的。用完后就的释放,下一个方法才能接着使用。

  1. @Test
  2. void range() {
  3. MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
  4. //打桩
  5. demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));
  6. Assertions.assertTrue(StaticUtils.range(2,6).contains(11));
  7. //关闭
  8. demo.close();
  9. }
  10. // 无参静态方法构建
  11. @Test
  12. void name() {
  13. MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
  14. //打桩
  15. demo.when(StaticUtils::name).thenReturn("dhmw");
  16. Assertions.assertEquals("dhmw",StaticUtils.name());
  17. //关闭
  18. demo.close();
  19. }

9、提升测试覆盖率

  1. 案例:数据统计系统,地推人员输入客户的姓名和手机号码,最后构建用户对象存入数据表。
  2. 1、业务代码如下:
  1. package com.echo.mockito.service.impl;
  2. import com.echo.mockito.dao.UserDao;
  3. import com.echo.mockito.service.RegistrationService;
  4. import com.echo.mockito.vo.User;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import javax.xml.bind.ValidationException;
  7. import java.sql.SQLException;
  8. public class RegistrationServiceImpl implements RegistrationService {
  9. @Autowired
  10. UserDao userDao;
  11. @Override
  12. public User register(String name, String phone) throws Exception {
  13. if (name == null || name.length() == 0){
  14. throw new ValidationException("name 不能为空");
  15. }
  16. if (phone == null || phone.length() ==0 ){
  17. throw new ValidationException("phone 不能为空");
  18. }
  19. User user;
  20. try {
  21. user = userDao.save(name,phone);
  22. }catch (Exception e){
  23. throw new Exception("SqlException thrown" + e.getMessage());
  24. }
  25. return user;
  26. }
  27. }
  1. package com.echo.mockito.dao;
  2. import com.echo.mockito.vo.User;
  3. public class UserDao {
  4. public User save(String name,String phnoe){
  5. User user = new User();
  6. user.setName(name);
  7. return user;
  8. }
  9. }

2、生成相应的测试代码,此时有几个问题需要思考。

  1. 1、测试的类是RegistrationServiceImpl但是里面的userDao如何注入到测试类中?
  2. 2、因为需要测试覆盖度,需要考虑该测试类中总共有几种情况需要测试。
  3. 1):两个if 抛出了两个异常,总共是2种情况要测试。
  4. (2): 保存数据库分为正常保存和异常保存,总共2种情况测试。
  5. 综上所述,必须完成这四种情况的测试,才能覆盖所有代码。

3、相同的方法,我们生成测试用例.代码中有详细的说明,每一种情况都有测试用例。

  1. package com.echo.mockito.service.impl;
  2. import com.echo.mockito.dao.UserDao;
  3. import com.echo.mockito.vo.User;
  4. import org.junit.jupiter.api.Assertions;
  5. import org.junit.jupiter.api.BeforeEach;
  6. import org.junit.jupiter.api.Test;
  7. import org.mockito.*;
  8. import javax.xml.bind.ValidationException;
  9. import java.sql.SQLException;
  10. import static org.junit.jupiter.api.Assertions.*;
  11. class RegistrationServiceImplTest {
  12. @InjectMocks //RegistrationServiceImpl 实例中注入@Mock标记的类,此处是注入userDao
  13. @Spy
  14. private RegistrationServiceImpl registrationService;
  15. @Mock
  16. private UserDao userDao;
  17. @BeforeEach
  18. void setUp() {
  19. MockitoAnnotations.openMocks(this);
  20. }
  21. @Test
  22. void register() throws Exception {
  23. // ------------------ 第一种 name 异常情况 测试 start ------------------------
  24. String name = null;
  25. String phone = "1234";
  26. try {
  27. registrationService.register(name,phone);
  28. }catch (Exception e){
  29. Assertions.assertTrue(e instanceof ValidationException);
  30. }
  31. // ------------------ name 异常情况 测试 end ------------------------
  32. // ------------------ 第二种 phone 异常情况 测试 start ------------------------
  33. name = "111";
  34. phone = null;
  35. try {
  36. registrationService.register(name,phone);
  37. }catch (Exception e){
  38. Assertions.assertTrue(e instanceof ValidationException);
  39. }
  40. // ------------------ phone 异常情况 测试 start ------------------------
  41. // ------------------ 第三种 userDao.save 正常情况 测试 start ------------------------
  42. name = "111";
  43. phone = "111";
  44. //正常保存测试 打桩 走真实的方法
  45. Mockito.when(userDao.save(name,phone)).thenCallRealMethod();
  46. User user = registrationService.register(name,phone);
  47. Assertions.assertEquals("111",user.getName());
  48. // ------------------ userDao.save 正常情况 测试 end ------------------------
  49. // ------------------ 第四种 userDao.save 异常情况 测试 start ------------------------
  50. //异常保存测试 打桩 通过thenThrow 抛出异常 测试异常是否被捕获
  51. Mockito.when(userDao.save(name,phone)).thenThrow(new RuntimeException());
  52. try {
  53. registrationService.register(name,phone);
  54. }catch (Exception e){
  55. Assertions.assertTrue(e instanceof Exception);
  56. }
  57. // ------------------ userDao.save 异常情况 测试 end ------------------------
  58. }
  59. }

如上所示:上面提到的第一个问题,如何注入测试类中的成员变量,是通过**@InjectMocks** 注解即可完成。

测试覆盖率方法如下:

测试结果如下:

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

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

综上所述就是覆盖测试的方法,总结如下:

1、根据业务代码,分析出所有需要测试的情况。

  1. 2、根据不同的测试情况,编写具体的测试代码。
  2. 3、针对每一种情况,可以编写具体的测试代码,然后通过打桩,断言等方式,穷尽所有的清册情况即可。

问题:

  1. 1、如果真实代码 方法级别有 throws Exception 那么同样的,测试方法也必须方法级别要抛出异常,不然测试会报错。
  1. @Test
  2. void register() throws Exception {
  1. 2、保存数据库分为正常和异常,那么先测正常分支,然后在测试异常分支,如果顺序反了,测试先抛出异常,正常的分支就不会执行,这样会导致测试覆盖不全。
标签: 单元测试

本文转载自: https://blog.csdn.net/weixin_39327556/article/details/125292255
版权归原作者 丹辉美文 所有, 如有侵权,请联系我们删除。

“mock详细教程入门这一篇就够了”的评论:

还没有评论