如何完成单元测试:Mockito+Junit 使用实战

       相信大家在不少书籍、文章中都看过大佬们强调单元测试如何如何重要,一写大公司也会强调单元测试的重要性。但是如何完成单元测试却鲜有文章提及,这对一写萌新开发就相当不友好了。

        这篇文章我就使用Mockito和Junit这两个工具,给大家分享一下。如果有大佬发现哪里有谬误,欢迎指正(#^.^#)。

为什么要使用mockito

        上图目录结构,想必大家都不陌生。如果没有单元测试。各位小伙伴,如何完成功能的验证呢?

        有的小伙伴可能会想着把项目运行起来,调用Controller 对应的接口,通过debug来查看对应功能是否符合预期。

         我只想说并不是所有时候,你都能在本机启动整个项目的。而且,就算你本地运行的起来这个项目,当一个功能同时由多个人共同开发时,功能不符合预期,到底是我们负责的部分的问题,还是其他伙伴负责的功能有问题呢?你打算阅读其他伙伴的功能吗?此外,当功能升级需要向下兼容时,这种方式也无法保证。

        好的,现在你也意识到了,我应该只测试我编辑的内容就足够了。但是我的Service有依赖其他bean呀。没有spring帮我们注入,难道我们手动实例化吗?好的,但是依赖的Bean又依赖了其他bean, 甚至是个mapper怎么办?

package org.example.service.impl;

import lombok.Setter;
import org.example.business.DemoBusinessOne;
import org.example.business.DemoBusinessTwo;
import org.example.pojo.bo.DemoBO;
import org.example.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class DemoServiceImpl implements DemoService {

    @Setter(onMethod_ = @Autowired)
    private DemoBusinessOne demoBusinessOne;

    @Setter(onMethod_ = @Autowired)
    private DemoBusinessTwo demoBusinessTwo;


    @Override
    public String getDemo() {
        DemoBO demoBO = demoBusinessOne.doBusiness1();
        DemoBO demoBO1 = demoBusinessTwo.doBusiness2();
        return  Optional.ofNullable(demoBO).map(DemoBO::getBusinessMessage).orElse("") 
                + Optional.ofNullable(demoBO1).map(DemoBO::getBusinessMessage).orElse("");
    }
}

        这个时候mockito,就发挥作用了。先看一个简单的示例:

package org.example.service.impl;

import org.example.business.DemoBusinessOne;
import org.example.business.DemoBusinessTwo;
import org.example.pojo.bo.DemoBO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

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

// 开启 Mockito
@RunWith(MockitoJUnitRunner.class)
public class DemoServiceImplTest {

    @InjectMocks // 需要mockito 帮我们注入依赖的对象,也就是我们要测试的对象
    private DemoServiceImpl demoService;
    @Mock //依赖对象 通常我们不关心他的具体实现逻辑
    private DemoBusinessOne demoBusinessOne;
    @Mock
    private DemoBusinessTwo demoBusinessTwo;


    @Test
    public void getDemoTest() {
        // 当调用mock对象的方法时,我们让他默认返回一个对象
        Mockito.when(demoBusinessOne.doBusiness1()).thenReturn(new DemoBO());
        Mockito.when(demoBusinessTwo.doBusiness2()).thenReturn(new DemoBO());

        assertNotNull(demoService.getDemo());
    }

}

导入依赖

   这里我使用的junit,版本是 4.12,mockito版本为3.9.0。

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.9.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>3.9.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>3.9.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>

mockito使用案例

关于spy和mock

        如果你是第一次接触mockito,很有可能被一个概念搞晕(起码我刚接触的时候是这样的)——什么是spy什么是mock。简单的理解就是我们依赖的那个对象,如刚开始的那个例子中的demoBusinessOne、demoBusinessTwo,这俩的实例是由谁完成的。

        如果我没有创建对象,可以使用mock,此时mockito框架会通过帮我们创建一个对象,我叫他mock对象。这个mock对象中的方法被调用时,默认返回空,当然我们可以自定义他调用时候的返回值。就像最开始的返回值。甚至可以自定义一段执行逻辑。如下:

    @Test
    public void customInstanceTest() {
        Mockito.when(demoBusinessOne.doBusiness1()).thenReturn(new DemoBO());

        Mockito.when(demoBusinessTwo.doBusiness2()).then((invocation) -> {
            // 如果要使用参数可以使用 invocation.getArgument(0) 获取
            DemoBO demoBO = new DemoBO();
            demoBO.setBusinessMessage("Custom Message");
            return demoBO;
        });

        assertEquals("Custom Message", demoService.getDemo());
    }

        如果我已经有一个实例对象,需要用把这个实例加入到mockito框架中时,可以使用spy。

这么做,可以在调用实例对象的某个方法时调用实例真实的方法,如下面代码片段所示。

    @InjectMocks // 需要mockito 帮我们注入依赖的对象,也就是我们要测试的对象
    private DemoServiceImpl demoService;

//    @Mock //依赖对象 通常我们不关心他的具体实现逻辑
    @Spy
    private DemoBusinessOne demoBusinessOne = new DemoBusinessTwoImpl();
    @Mock
    private DemoBusinessTwo demoBusinessTwo;

    @Test
    public void spyTest(){
        Mockito.when(demoBusinessOne.doBusiness1()).thenCallRealMethod();
        Mockito.when(demoBusinessTwo.doBusiness2()).thenReturn(new DemoBO());

        assertEquals("realBusiness",demoService.getDemo());
    }

        分享一个我常用spy的场景,在测试一写业务代码时,总是很烦造一写模拟数据,利用mybatis 加上spy 就可以轻松的使用开发环境数据库中的数据了。

        需要特别注意的一个点,使用spy()创建的对象时,如果想要设置调用方法时候返回的之,不要使用thenReturn。如下面代码

    List list = new LinkedList();
    List spy = spy(list);
 
    //会调用真正的方法,然后抛出数组越界异常
    when(spy.get(0)).thenReturn("foo");
 
    //应该这样使用
    doReturn("foo").when(spy).get(0);

Mockito 常用方法

        这里仅介绍一些常用方法,具体使用还是得看官方文档

  • mock()
//手动创建一个mock对象 可以使用 @Mock 注解替代
DemoBusinessOne demoBusinessOne = Mockito.mock(DemoBusinessOne.class);
  • spy()
// 窃取一个实际对象作为mock对象,可以使用 @Spy 注解替代
DemoBusinessOne demoBusinessOne = Mockito.spy(new DemoBusinessTwoImpl());
  • 以下三组配合使用

       定义满足什么条件:when()

       定义满足条件后的行为:then()、thenReturn()、thenAnswer()、thenThrow()、thenCallRealMethod()

       定义满足条件后的行为:do()、doReturn()、doAnswer()、doThrow()、doCallRealMethod()

  • when 扩展

       对于有参数的方法,我们希望根据参数进行不同的处理时,可以采用下面的方式:

    @Test
    public void argTest(){
        DemoBusinessThree demoBusinessThree = Mockito.mock(DemoBusinessThree.class);
        Mockito.when(demoBusinessThree.hasArgBusiness("test1")).thenReturn(new DemoBO());
        Mockito.when(demoBusinessThree.hasArgBusiness("test2")).thenReturn(null);

        assertNotNull(demoBusinessThree.hasArgBusiness("test1"));
        assertNull(demoBusinessThree.hasArgBusiness("test2"));
    }

        此外mockito还帮我们提供了更多参数匹配规则。详情请查看ArgumentMatchers类下的方法,很简单这里不做赘述。

  • verify()

        verify用于验证mock对象是否完成某个行为。

  • verify 行为定义(常用)

        times(i)                                            是否执行了i次

        atLeastOnce()                                 至少执行了一次

        atLeast(i)                                         至少执行了i次

        atMostOnce()                                  最多执行了一次

        atMost(i)                                          最多执行了i次

        timeout(i)                                         验证异步方法,如果异步方法在i内没有执行会验证失败。

        after(i)                                              在i时间之后验证方法时候执行

结语

        《代码整洁之道》中有一句话我觉着很有道理:糟糕的单元测试不如没有测试。一个好的单元测试最起码应该满足三点:自动化、独立、可重复。当然还有一些其他大佬提出了其他原则比如FIRST,感兴趣的伙伴可以自己了解一下。

        通过Junit的Assert工具和Mockito的verify()的api可以,帮助我们完成自动化。通过Mock 和spy 可以帮助我们轻松的完成独立。

        以上就是我分享的内容,当然只是一些基础的示例,想要写出优秀的单元测试,能够顺应系统的不断迭代,在我看来是一件相当需要功底的事情,如果有机会再和大家分享吧。

        

  • 30
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值