关于SpringBoot框架下的service层单元测试问题(mockito)

xiaoxiao2021-02-28  73

这是大牛的网址01 02

mockito的官方文档:

  关于Junit测试业务逻辑层中出现的【方法的输入输出没有规范、测试高度依赖spring boot上下文环境、测试用例不完整等】这些问题,我们使用更完整的测试方法来解决。

学习原因:

  针对最近遇到的问题:在SpringBoot框架下,如何脱离Spring的环境进行service层的单元测试,同时面临着【方法的输入输出没有规范、测试高度依赖spring boot上下文环境、测试用例不完整等】这些问题,查了很多资料之后,发现mockito可以很好的解决我当前遇到的问题。因为在这个过程中,查询的资料由于使用的mockito的版本不一致,还有写代码使用的框架不一致,导致在学习和实践测试的过程中走了很多弯路。现在总结如下,提供大家使用,少走弯路。   当前我的代码框架是:SpringBoot下,采用SpringMVC三层架构模式,使用SpringDataJPA处理简化DAO层的编写,语言为kotlin。


一、单元测试的目标和挑战

  单元测试的思路是在不涉及依赖关系的情况下测试代码(隔离性),所以测试代码与其他类或者系统的关系应该尽量被消除。一个可行的消除方法是替换掉依赖类(测试替换),也就是说我们可以使用替身来替换掉真正的依赖对象。  使用Mockito可以明显的简化对外部依赖的测试类的开发。

二、mockito细节讲解

2.1 基于Mockito的Mock测试

维基百科对Mock object定义如下:

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways.   Mock测试用虚拟的对象来代替真实对象来完成测试工作。为什么要用虚拟对象来代替真实对象?一是因为由于开发分工的问题,导致测试时真实的对象并不存在,二是因为真实对象的行为不可预知,三是可能真实对象难以创建,四是由于真实对象的响应可能很慢。

  通常的单元测试仅仅测试方法的结果,Mock测试在此之上能够测试方法的行为,例如某个方法是否被调用或者某方法被调用的次数等。

三、mockito的使用步骤

  Mockito是一个用于java程序的Mock测试框架,相对于easymock等Mock框架,采用Mockito框架的测试代码更加简洁,可读性更强。在pom文件中添加依赖既可使用。

3.1 添加mockito的maven依赖

  需要在 Maven 声明依赖,你可以在 http://search.maven.org 网站中搜索    g:”org.mockito”, a:”mockito-core” 来得到具体的声明方式。   下面是2.0.2版本的mockito:

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>2.0.2-beta</version> <scope>test</scope> </dependency>
3.2 测试依赖环境的定义

目前为止,有两种方法可以初始化 fields: (1)Mockito 现在提供一个 JUnit rule。 使用 Mockito 提供的注解比如 @Mock, @Spy, @InjectMocks 等等。 (2)用 @RunWith(@MockitoJUnitRunner.class) 标注 JUnit 测试类 在 @Before 之前调用 MockitoAnnotations.initMocks(Object) 现在你可以选择使用一个 rule:

@RunWith(@MockitoJUnitRunner.class) public class TheTest { @Rule public MockitoRule mockito = MockitoJUnit.rule(); // ... }
3.3 使用mockito创建和配置mock对象

  @mock为一个interface提供一个虚拟的实现。   @InjectMocks将本test类中的mock(或@mock)注入到被标注的对象中去,也就是说被标注的对象中需要使用标注了mock(或@mock)的对象。   mockito遇到使用注解的字段会调用MockitoAnnotations.initMocks(this) 来初始化该 mock 对象。另外也可以通过使用@RunWith(MockitoJUnitRunner.class)来达到相同的效果。 代码见3.4.3

3.4 在测试方法中配置mock

使用测试桩stub来定义在service的实现代码中使用到的Repository代码的设定返回值: 3.4.1 DAO层

interface ProjectRepository : JpaRepository<Project, tag_t> { fun findByProjectId(projectId: String): Project } //JpaRepository是SpringDataJPA提供的一个类,里面有自定义的方法,我们集成之后直接调用即可,这个不重要,可以直接跳过不要看 @NoRepositoryBean public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> { List<T> findAll(); List<T> findAll(Sort var1); List<T> findAllById(Iterable<ID> var1); <S extends T> List<S> saveAll(Iterable<S> var1); void flush(); <S extends T> S saveAndFlush(S var1); void deleteInBatch(Iterable<T> var1); void deleteAllInBatch(); T getOne(ID var1); <S extends T> List<S> findAll(Example<S> var1); <S extends T> List<S> findAll(Example<S> var1, Sort var2); }

3.4.2 service层的实现

//service接口 interface AaaService{ fun create(a:Aaa):Aaa } //service层的实现 //注入Aaa类的DAO层 @resource private lateinit var aRepository:AaaRepository class AaaServiceImpl:Aaa{ override fun create(a:Aaa):Aaa{ return aRepository.save(a) } }

3.4.3 测试类代码:

@RunWith(MockitoJUnitRunner::class) class AaaServiceImplTest { //用于定义被Mock的组件 @Mock private lateinit var aaaRepository: AaaRepository //mock一个要测试的类对象,同时@Mock注解的会被依赖注入到@InjectMocks注解的类对象中 @InjectMocks private lateinit var aaaService: ProjectServiceImpl private lateinit var aaa:Aaa @Before fun setUp() { //用于初始化@Mock注解修饰的组件 MockitoAnnotations.initMocks(this) //定义类对象 aaa = Aaa() } @Test fun create() { //这是自定义的一个测试桩stub,定义在service层关于dao层语句的返回值定义,使得service代码的测试脱离开dao层。 Mockito.`when`(aaaRepository.save(aaa)).thenReturn(aaa) //运行方法 val result = aaaService.create(aaa) //这里可以多验证几种结果 assertEquals(aaa, result) assertEquals(projectInfo.projectId, result.projectId) //判断某个方法是否被调用(是否发生交互) Mockito.verify(aaaRepository).save(aaa) } }

  至此,mockito的测试步骤就完了,更多的小的杂乱的知识点,在上面给出的网址中。下面我们来看看在测试类中对测试更全面的介绍。


四、多项测试简介

下面结合Mockito工具的使用来谈谈Mock中涉及一些重要概念。

Stub:

  A stub is a controllable replacement for an existing dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly.   我的理解,Stub用于绕开实际依赖或者某些实际方法的执行。可以用Stub去伪造一个方法来绕过数据库访问方法的执行。在Mockito中提供了when语句来实现Stub。例如when(a.func()).thenReturn(1)伪造执行a.func(),当调用a.func()时返回值定义为1。

Behavior verification:   Mock测试区别于一般单元测试方法的重要特点是Mock测试可以进行行为验证。在mockito中采用verify(a, times(n)).func()来验证对象a的func()方法是否被调用n次。

Wrap a real object: Mock对象只能调用Stub方法,而不能调用其真实方法,否则会抛出空指针异常。而Mockito提供了spy机制可以用于监控一个真实对象,此时可以调用该对象的真实方法。在Mockito中可以采用@Spy标签或者调用Mockito.spy(T object)方法。

Mock中常用注解有:

@Mock:用于标识mock对象。@InjectMocks:将用@Mock标注的mock对象,注入到被某个被该注解标注的测试的对象中。@Spy:用来标注某个被@Mockito标注的真实对象。
4.2 mockito的限制

Mockito当然也有一定的限制。而下面三种数据类型则不能够被测试

final classes anonymous classes primitive types


doReturn().when()和when().thenReturn()的区别。后者会调用对象的真实api,而前者遇到函数调用直接返回doReturn中设置的值。

参考:参考的部分内容

更详细的内容最好是参考:mockito的官方文档

转载请注明原文地址: https://www.6miu.com/read-21551.html

最新回复(0)