单元测试Mock框架--Mockito

目前开发中,单元测试遇到的问题

在业务代码开发完成以后,需要对新增代码进行单元测试,由于项目依赖的第三方组件以及外部系统接口较多,每次执行单元测试时都需要启动整个项目,加载各种依赖,而且由于网络限制有时还需要申请各种ACL,且项目启动耗时较长,有时仅仅为了跑一个仅有几行代码的单元测试,却要耗时几分钟等待项目的启动,严重违背单元测试的初衷。这种情况导致了大家写单元测试的积极性不高,甚至直接跳过单元测试,直接把代码发布到测试环境进行集成测试。

解决方案–Mock

上述问题有没有解决方案呐,答案是肯定是有的,那就是Mock,对外部依赖进行mock,仅执行自己的代码。目前mock的工具有很多,像PowerMock,EasyMock,Mockito等。下面针对Mockito的使用方法做一个简单的介绍

Junit4 + Mockito:

先来看一个例子:

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserInfoMapper userInfoMapper;
    @Resource
    private WhiteListCheckService whiteListCheckService;
    @Resource
    private UserLevelRemoteProxy userLevelRemoteProxy;
    
    @Override
    public UserInfoBo queryUserInfo(String userId){
        UserInfoEntity userInfo = userInfoMapper.findById(userId);
        boolean isWhiteListUser = whiteListCheckService.isWhiteListUser(userId);
        String userLevel = userLevelRemoteProxy.queryUserLevelById(userId);
        UserInfoBo userInfoBo = BeanUtils.copy(UserInfoBo.class, userInfo);
        userInfoBo.setUserLevel(userLevel);
        userInfoBo.isWhiteListUser(isWhiteListUser);
        
        return userInfoBo;
    }
}

上面是一个常用类型的service方法,只是省略了一些业务逻辑,方法中会调用mapper方法查询数据库,系统内部其他service方法,还有外部接口.现在我们使用Junit4+Mockito来对该方法进行单元测试:

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest{
    @InjectMocks
    private UserServiceImpl userService;
    @Mock
    private UserInfoMapper userInfoMapper;
    @Mock
    private UserLevelRemoteProxy userLevelRemoteProxy;
    
    @Test
    public void queryUserInfoTest(){
        UserInfoEntity userInfo = Mockito.mock(UserInfoEntity.class);
        Mockito.when(whiteListCheckService.isWhiteListUser(Mockito.anyString))
            .thenReturn(true);
        Mockito.when(userInfoMapper.findById(Mockito.anyString())).thenReturn(userInfo);
        Mockito.when(userLevelRemoteProxy.queryUserLevelById(Mockito.anyString()))
            .thenReturn("12Lev");
        UserInfoBo userInfoBo = userService.queryUserInfo("123");
        
        Assert.assertEquals("12Lev", userInfoBo.getUserLevel());
        
    }
}

执行上述方法,会在执行userInfoMapper.findById方法时,返回我们mock的UserInfoEntity的实例,当执行whiteListCheckService.isWhiteListUser方法时,会返回true,执行userLevelRemoteProxy.queryUserLevelById方法时,会返回12Lev。我们在把依赖的接口进行Mock以后,就可以在不启动项目的前提下执行该单元测试了,进而提高了单元测试的执行速度。同时也可以对代码里的不同分支编写多个单元测试,进一步提升代码的单元测试覆盖率。

Mockito常用注解:
  • @Mock :创建一个Mock实例
  • @InjectMocks :会把@Mock@Spy注解的对象自动注入进来,一般用于创建需要被测试的对象实例
  • @Spy:允许创建部分模拟的对象
Mockito常用方法:
  • 参数匹配器:Mockito.anyString()/Mockito.anyInt()/Mockito.any()
  • 方法执行校验器:Mockito.verify()
  • 锚点方法调用及指定返回值:Mockito.when(mock.someMethod()).thenThrow(new RuntimeException).thenReturn("foo")
  • 执行真实实例方法:Mockito.spy()

更多方法请参考官方文档: https://javadoc.io/doc/org.mockito/mockito-core/latest/index-files/index-1.html

Tips:
  • Java 8 Lambda 匹配器的支持(Since 2.1.0):

    @Test
    public void testMethod(){
        List<String> list = Mockito.mock(List.class);
        list.add("111");
        list.add("222");
        list.add("333");
        Mockito.verify(list,Mockito.times(3)).add(Mockito.argThat(s -> s.length() <5));//list中最多被添加4个元素
    }
    
  • Mocking final type, enums and final methods (Since 2.1.0)

    Mockito从2.1.0版本开始支持对final type, enums and final methods进行mock,但是需要额外的配置,详情请参考: Mocking final types, enums and final methods

  • 对静态方法进行Mock(Since 3.4.0)

    需要把mockito-core依赖替换成mockito-inline,且对jdk版本有要求,若使用是jdk8或更早版本的,需要使用到ByteBuddy 依赖

    		<dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-inline</artifactId>
                <version>3.7.7</version>
                <scope>test</scope>
            </dependency>
            <!-- 若使用的是jdk8或更早版本,需要添加如下依赖 -->
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy</artifactId>
                <version>1.12.1</version>
            </dependency>
            <!-- 若使用的是jdk8或更早版本,需要添加如下依赖 -->
            <dependency>
                <groupId>net.bytebuddy</groupId>
                <artifactId>byte-buddy-agent</artifactId>
                <version>1.12.1</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13</version>
            </dependency>
    
    	@Test
        public void mockStaticTest(){
            LocalDate yearOf2000 = LocalDate.of(2000, 1, 1);
            try (MockedStatic theMock = Mockito.mockStatic(LocalDate.class)) {
                theMock.when(LocalDate::now).thenReturn(yearOf2000);
                Assert.assertEquals(2000, LocalDate.now().getYear());
            }
        }
    
  • 对于私有方法的mock

    mockito是不支持对是由方法进行mock的,如果有这方面的需求,可以结合使用ProwerMock来做,至于mockito为什么不支持对私有方法进行mock,官方的说法如下:

    Mockito And Private Methods

总结

关于写单元测试,很多人认为是一件性价比不高的事情,与其花费大量时间对一些一眼就能看出执行结果的方法编写单元测试,还不如直接进行集成测试,跑一下主流程,没问题就可以提测了,但是这样做往往会忽略分支流程的测试覆盖,导致提测的代码质量不高,甚至会成为导致线上问题的隐患。所以,个人认为编写单元测试还是很有必要的,至少,要对新增代码进行单元测试覆盖。另外,现在有一些测试代码生成插件,可以帮助我们直接生成单元测试,大家感兴趣的话,可以尝试使用一下

TestMe

JCode5

Diffblue

参考

Mockito官网

360数科中间件团队博客中关于单元测试的文章

Mockito官方demo

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值