0


单元测试-SpringBoot Test和Mock

单元测试-SpringBoot Test和Mock

“单元测试”

“junit,mock,桩”

1. 什么是单元测试

定义:是指对软件中的最小可测试单元进行检查和验证。

Java里单元指一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

2. 单元测试与Spring Boot

2.1 引入依赖spring-boot-starter-test

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
spring-boot-starter-test中包含了junit和mockito等依赖

​​在这里插入图片描述

2.2 相关依赖

  • junit – 标准的单元测试Java应用程序
  • Spring Test & Spring Boot Test – 对Spring Boot应用程序的单元测试提供支持
  • Mockito, Java mocking框架,用于模拟任何Spring管理的Bean,比如在单元测试中模拟一个第三方系统Service接口返回的数据,而不会去真正调用第三方系统;
  • AssertJ,一个流畅的assertion库,同时也提供了更多的期望值与测试返回值的比较方式;
  • JSONassert,对JSON对象或者JSON字符串断言的库。
  • …………

2.3 标准的Spring Boot单元测试结构

@DisplayName("AlarmMsgstationController测试类")//起别名@SpringBootTest@AutoConfigureMockMvc@Transactional@TestMethodOrder(MethodOrderer.OrderAnnotation.class)publicclassTest{}

3. SpringBoot Test常用注解

在这里插入图片描述

4. 基本用法

类上添加注解,启动Spring Boot环境

@RunWith(SpringRunner.class)@SpringBootTestpublicclassFirstTest{@Testpublicvoidtest(){int a=1;Assertions.assertEquals(1,a);//判断二者是否相等}}

可以使用Assertions类来判断结果是否符合预期;

4.1 直接注入

对数据访问层(Service层同理)

对数据访问层Dao中的方法进行测试
在这里插入图片描述
ps:@Autowired直接注入的方法会真实操作数据库,如果在单元测试中不想改变数据数据库中的值,不能使用直接注入的方法

其实可以在类上再添加这两个注解,通过@Transactional可以知道调用了数据库,对其操作进行回滚

但是如果项目中使用了@Component注解(在SpringBoot项目启动的时候就会跟着实例化/启动),@Component注解的类里有多线程方法,那么在执行单元测试的时候,由于多线程任务的影响,就可能对数据库造成了数据修改,即使使用了事务回滚注解@Transactional。(我在百度上看到的,没找到具体的测试方法,所以没试)

@Component注解:带此注解的类看为组件,当使用基于该注解的配置和类路径扫描的时候,这些类就会被实例化。

@Transactional@Rollback(true)// 事务自动回滚,默认是true。可以不写

4.2 Mock注入

实现原理:使用Stub(桩)技术动态的替换原程序的功能。

直接跑Java代码,不需要启用Spring及连接数据库,模拟一切操作数据库的步骤,不执行任何SQL,也可以模拟任何返回值

4.2.1 使用Mock的优点:

  1. 可以完全脱离数据库
  2. 只针对某一个小方法(一个小的单元)来测试,测试过程中,不需要启动其他的东西,不免其他因素可能产生的干扰

4.2.2 编写Mock代码

  1. 不再使用@Autowired启动Spring会导致运行单元测试的时候的速度变慢(run->Junit Test),单元测试只针对某一个类的方法来测试,不需要启动Spring,只需要对应的实体实例就够了,在需要注入bean的时候直接new
  2. 不再使用@SpringBootTest
  3. 不调用数据库@Transactional @Rollback(true)这两个注解也不要
  4. 使用Assert断言
基本应用:
mock 对象的方法的返回值默认都是返回类型的默认值
importorg.junit.Assert;importorg.junit.Test;importjava.util.Random;importstaticorg.mockito.Mockito.*;publicclassMockitoDemo{@Testpublicvoidtest(){Random mockRandom =mock(Random.class);//mock了一个Random对象Assert.assertEquals(0, mockRandom.nextInt());//未进行打桩,每次返回值都是0when(mockRandom.nextInt()).thenReturn(100);// 进行打桩操作,指定调用 nextInt 方法时,永远返回 100Assert.assertEquals(100, mockRandom.nextInt());}}

4.2.3 Mock的注解和常用的方法

@Mock

@Mock 注解可以理解为对 mock 方法的一个替代。使用该注解时,要使用

MockitoAnnotations.initMocks

​ 方法,让注解生效。旧版的是initMocks,新版的是openMocks

importorg.junit.Assert;importorg.junit.Before;importorg.junit.Test;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importjava.util.Random;importstaticorg.mockito.Mockito.*;publicclassMockitoDemo{@MockprivateRandom random;@Beforepublicvoidbefore(){// 让注解生效MockitoAnnotations.initMocks(this);}@Testpublicvoidtest(){when(random.nextInt()).thenReturn(100);Assert.assertEquals(100, random.nextInt());}}

也可以用

MockitoJUnitRunner

​来代替

MockitoAnnotations.initMocks

importorg.junit.Assert;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.Mock;importorg.mockito.junit.MockitoJUnitRunner;importjava.util.Random;importstaticorg.mockito.Mockito.*;@RunWith(MockitoJUnitRunner.class)publicclassMockitoDemo{@MockprivateRandom random;@Testpublicvoidtest(){when(random.nextInt()).thenReturn(100);Assert.assertEquals(100, random.nextInt());}}
@Spy

mock()方法与spy()方法的不同:

  1. 被spy的对象会走真实的方法,而mock对象不会
  2. spy方法的参数是对象实例,mock的参数是class
@InjectMocks

mockito 会将

@Mock

​、

@Spy

​ 修饰的对象自动注入到

@InjectMocks

​ 修饰的对象中

thenReturn

thenReturn 用来指定特定函数和参数调用的返回值;

thenReturn 中可以指定多个返回值。在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。

doReturn 的作用和 thenReturn 相同,但使用方式不同:

when(mockRandom.nextInt()).thenReturn(1);//返回值为1when(mockRandom.nextInt()).thenReturn(1,2,3);doReturn(1).when(random).nextInt();
thenThrow

thenThrow 用来让函数调用抛出异常。(可搭配try catch使用)

thenThrow 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。

when(mockRandom.nextInt()).thenThrow(newRuntimeException("异常"));when(mockRandom.nextInt()).thenThrow(newRuntimeException("异常1"),newRuntimeException("异常2"));@Testpublicvoidtest(){Random mockRandom =mock(Random.class);when(mockRandom.nextInt()).thenThrow(newRuntimeException("异常1"),newRuntimeException("异常2"));try{
            mockRandom.nextInt();Assert.fail();//上一行会抛出异常,到catch中去,走不到这里}catch(Exception ex){Assert.assertTrue(ex instanceofRuntimeException);Assert.assertEquals("异常1", ex.getMessage());}try{
            mockRandom.nextInt();Assert.fail();}catch(Exception ex){Assert.assertTrue(ex instanceofRuntimeException);Assert.assertEquals("异常2", ex.getMessage());}}

对应返回类型是 void 的函数,thenThrow 是无效的,要使用 doThrow。也可以用 doThrow 让返回非void的函数抛出异常

doThrow(newRuntimeException("异常")).when(exampleService).hello();// 下面这句等同于 when(random.nextInt()).thenThrow(new RuntimeException("异常"));doThrow(newRuntimeException("异常")).when(random).nextInt();
reset

使用 reset 方法,可以重置之前自定义的返回值和异常。

reset(exampleService);
vetify

使用 verify 可以校验 mock 对象是否发生过某些操作,配合 time 方法,可以校验某些操作发生的次数

//判断backOutstockMapper.selectReportCountByMap()方法是否被调用1次verify(backOutstockMapper,times(1)).selectReportCountByMap(Mockito.any());//校验backOutstockMapper.selectReportCountByMap()方法是否被调用过verify(backOutstockMapper).selectReportCountByMap(Mockito.any());

4.2.5 断言

->assertTrue(String message,boolean condition)             要求condition ==true->assertFalse(String message,boolean condition)            要求condition ==false->assertEquals(String message,XXX expected,XXX actual) 要求expected期望的值能够等于actual
->assertArrayEquals(String message,XXX[] expecteds,XXX[] actuals) 要求expected.equalsArray(actual)->assertNotNull(String message,Object object)          要求object!=null->assertNull(String message,Object object)             要求object==null->assertSame(String message,Object expected,Object actual)     要求expected == actual
->assertNotSame(String message,Object unexpected,Object actual) 要求expected != actual
->assertThat(String reason,T actual,Matcher matcher)  要求matcher.matches(actual)==true->fail(String message) 要求执行的目标结构必然失败,同样要求代码不可达,即是这个方法在程序运行后不会成功返回,如果成功返回了则报错

4.3 Tips

  1. 对待类中私有的方法,可以用反射的方式进行测试
  2. 打包时跳过testmvn deploy -f pom_http.xml-jar -Dmaven.test.skip=true
  3. Mockito 默认是不支持静态方法,可使用 PowerMock 让 Mockito 支持静态方法(新增依赖)

5. 总结

单元测试测试的不是整条业务线,而是类中的单个方法单元。

按照单一性原则的话,一个方法只做一件事,那么针对这个方法的单元测试就简单了。

当多个方法单元测试的结果都没问题的时候,多个方法聚合成的业务链照理说也是没问题的,一个方法中依赖了其他方法的处理结果或返回结果,那么这个结果应当是可预测的,所以也是可以mock出所有场景的,而单元测试也应该覆盖到不同结果对应的场景。

单元测试除了测试代码逻辑外,最大的好处是可以检验整体设计是否合理。一个方法做了太多事的话,就会导致单元测试很难覆盖,比如service层的方法,如果入参的校验,业务逻辑的处理,不同数据表DB的操作,DB返回结果的校验处理全部在单一方法中实现,那对于后期业务的扩展、维护、问题的排查都不好进行;如果把以上说的那些全部分离出来,封装成一个个独立的方法,最后只在一个方法中总调,这样不仅单元测试比较好实现,而且后期的维护,扩展都会很容易。

ps:上面总结这段话不是我说的,是我在学习的过程中看到一位老哥写在评论区的。领导让我总结一下,我就抄过来敷衍领导了


本文转载自: https://blog.csdn.net/sinat_41900036/article/details/128551507
版权归原作者 Rigel爱科学 所有, 如有侵权,请联系我们删除。

“单元测试-SpringBoot Test和Mock”的评论:

还没有评论