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

这是大牛的网址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的官方文档

猜你喜欢

转载自blog.csdn.net/amethyst128/article/details/77734360