目录

Mock测试详细教程入门这一篇就够了

Mock测试详细教程入门这一篇就够了

1、什么是

https://i-blog.csdnimg.cn/blog_migrate/2636627b2f156d557299f28e3dbff064.png

1.png

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

2、为什么要进行Mock测试

是为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在 单元测试 中,也会出现在集成测试、 系统测试 过程中。

Mock最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。

3、Mock适用场景

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

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

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

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

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

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

4、代码实例

新建测试工程

  1. package com.echo.mockito;

  2. public class demo {

  3. //新建一个测试方法

  4. public int add(int a,  int b){

  5. return a + b;

  6. }

  7. }

构建mock测试方法

选中测试类,右键选中generate

https://i-blog.csdnimg.cn/blog_migrate/b36fa41b13d1c32ef90d2ed1a12134b0.png

2.png

点击test

https://i-blog.csdnimg.cn/blog_migrate/5e742a1b6ddaec30edd6af55d6fe9aba.png

3.png

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

https://i-blog.csdnimg.cn/blog_migrate/cb14518f76e2ea7b74acc8cb8b1a39ea.png

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 ,测试不通过。

https://i-blog.csdnimg.cn/blog_migrate/3aedf90b93e6947df46563f22cf4295f.png

5.png

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

https://i-blog.csdnimg.cn/blog_migrate/0bef048f9c985ca52ba327bfea939f8c.png

6.png

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

https://i-blog.csdnimg.cn/blog_migrate/6ab9e65e617bbdec6b481187df61da27.png

7.png

7、测试方法说明

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

  • 打桩测试
  • 异常测试
  • 真实方法调用
  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静态方法

之前的版本中是不允许模拟测试静态方法的,如果需要测试静态方法,需要替换新的mock依赖,注释掉目前有的依赖,会有冲突。

静态方法测试要用mock的MockedStatic类构建测试方法。

  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>

  13. package com.echo.mockito.Util;

  14. import org.junit.jupiter.api.Assertions;

  15. import org.junit.jupiter.api.BeforeEach;

  16. import org.junit.jupiter.api.Test;

  17. import org.mockito.MockedStatic;

  18. import org.mockito.Mockito;

  19. import java.util.Arrays;

  20. import static org.junit.jupiter.api.Assertions.*;

  21. class StaticUtilsTest {

  22. @BeforeEach

  23. void setUp() {

  24. }

  25. // 有参静态方法构建

  26. @Test

  27. void range() {

  28. MockedStatic   demo = Mockito.mockStatic(StaticUtils.class);

  29. //打桩

  30. demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));

  31. Assertions.assertTrue(StaticUtils.range(2,6).contains(11));

  32. }

  33. // 无参静态方法构建

  34. @Test

  35. void name() {

  36. MockedStatic   demo = Mockito.mockStatic(StaticUtils.class);

  37. //打桩

  38. demo.when(StaticUtils::name).thenReturn("dhmw");

  39. Assertions.assertEquals("dhmw",StaticUtils.name());

  40. }

  41. }

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

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

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

https://i-blog.csdnimg.cn/blog_migrate/27ea684441c720fd3be708458d7f25df.png

8.png

解决:每个方法执行完毕后就直接关闭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. 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. }

  28. package com.echo.mockito.dao;

  29. import com.echo.mockito.vo.User;

  30. public class UserDao {

  31. public User save(String name,String phnoe){

  32. User user = new User();

  33. user.setName(name);

  34. return user;

  35. }

  36. }

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

1、测试的类是RegistrationServiceImpl但是里面的userDao如何注入到测试类中?

2、因为需要测试覆盖度,需要考虑该测试类中总共有几种情况需要测试。

(1):两个if抛出了两个异常,总共是2种情况要测试。

(2):保存 数据库 分为正常保存和异常保存,总共2种情况测试。

综上所述,必须完成这四种情况的测试,才能覆盖所有代码。

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

  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 注解即可完成。

测试覆盖率方法如下:

https://i-blog.csdnimg.cn/blog_migrate/436fc73b8af5645c1c460a45bdbb659e.png

9.png

测试结果如下:

1、右边部分会显示测试覆盖率。

2、真实代码绿色代表已覆盖测试,红色代表未覆盖测试。

https://i-blog.csdnimg.cn/blog_migrate/500267f4c751a0b09a13ac6fc2cf337e.png

11.png

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

https://i-blog.csdnimg.cn/blog_migrate/6939ed3c0f272c242e6043c535016313.png

12.png

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

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

2、根据不同的测试情况,编写具体的测试代码。

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

问题:

1、如果真实代码 方法级别有 throws Exception  那么同样的,测试方法也必须方法级别要抛出异常,不然测试会报错。

  1. @Test
  2. void register() throws Exception {

2、保存数据库分为正常和异常,那么先测正常分支,然后在测试异常分支,如果顺序反了,测试先抛出异常,正常的分支就不会执行,这样会导致测试覆盖不全。

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

https://img-blog.csdnimg.cn/direct/5fb98e37cc1b4f2bae72457589697c91.png

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些 是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

https://i-blog.csdnimg.cn/direct/514965e1ab1148dfb4c04768ea1b9cbd.jpeg

https://img-blog.csdnimg.cn/694b35de52e6493c99f913729355584f.png

视频文档获取方式:

这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。