Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >mock测试及jacoco覆盖率

mock测试及jacoco覆盖率

作者头像
菩提树下的杨过
发布于 2020-09-11 05:10:55
发布于 2020-09-11 05:10:55
4.5K80
代码可运行
举报
运行总次数:0
代码可运行

单元测试是保证项目代码质量的有力武器,但是有些业务场景,依赖的第三方没有测试环境,这时候该怎么做Unit Test呢,总不能直接生产环境硬来吧?

可以借助一些mock测试工具来解决这个难题(比如下面要讲的mockito),废话不多说,直奔主题:

一、准备示例Demo

假设有一个订单系统,用户可以创建订单,同时下单后要检测用户余额(如果余额不足,提醒用户充值),具体来说,里面有2个服务:OrderService、UserService,类图如下:

示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.cnblogs.yjmyzz.springbootdemo.service.impl;

import com.cnblogs.yjmyzz.springbootdemo.service.UserService;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

/**
 * @author 菩提树下的杨过
 */
@Service("userService")
public class UserServiceImpl implements UserService {


    @Override
    public BigDecimal queryBalance(int userId) {
        System.out.println("queryBalance=>userId:" + userId);
        //模拟返回100元余额
        return new BigDecimal(100);
    }
}

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.cnblogs.yjmyzz.springbootdemo.service.impl;

import com.cnblogs.yjmyzz.springbootdemo.service.OrderService;
import com.cnblogs.yjmyzz.springbootdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;

@Service("orderService")
public class OrderServiceImpl implements OrderService {

    @Autowired
    private UserService userService;

    /**
     * 下订单
     *
     * @param productName
     * @param orderNum
     * @return
     * @throws Exception
     */
    @Override
    public Long createOrder(String productName, Integer orderNum, int userId) throws Exception {
        System.out.println("createOrder=>userId:" + userId);
        if (StringUtils.isEmpty(productName)) {
            throw new Exception("productName is empty");
        }

        if (orderNum == null) {
            throw new Exception("orderNum is null!");
        }

        if (orderNum <= 0) {
            throw new Exception("orderNum must bigger than 0");
        }

        //下订单过程略,返回1L做为订单号
        Long orderId = 1L;

        //模拟检测余额
        BigDecimal balance = userService.queryBalance(userId);
        if (balance.compareTo(BigDecimal.TEN) <= 0) {
            System.out.println("余额不足10元,请及时充值!");
        }

        return orderId;
    }
}

里面的逻辑不是重点,随便看看就好。关注下createOrder方法,最后几行OrderService调用了UserService查询余额,即:OrderService依赖UserService,假设UserService就是一个第3方服务,不具备测试环境,本文就来讲讲如何对UserService进行mock测试。

二、pom引入mockito 及 jacoco plugin

2.1 引入mockito

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 <dependency>
2     <groupId>org.mockito</groupId>
3     <artifactId>mockito-all</artifactId>
4     <version>1.9.5</version>
5     <scope>test</scope>
6 </dependency>

mockito是一个mock工具库,马上会讲到用法。

2.2 引入jacoco插件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 1 <plugin>
 2     <groupId>org.jacoco</groupId>
 3     <artifactId>jacoco-maven-plugin</artifactId>
 4     <version>0.8.5</version>
 5     <executions>
 6         <execution>
 7             <id>prepare-agent</id>
 8             <goals>
 9                 <goal>prepare-agent</goal>
10             </goals>
11         </execution>
12         <execution>
13             <id>report</id>
14             <phase>prepare-package</phase>
15             <goals>
16                 <goal>report</goal>
17             </goals>
18         </execution>
19         <execution>
20             <id>post-unit-test</id>
21             <phase>test</phase>
22             <goals>
23                 <goal>report</goal>
24             </goals>
25             <configuration>
26                 <dataFile>target/jacoco.exec</dataFile>
27                 <outputDirectory>target/jacoco-ut</outputDirectory>
28             </configuration>
29         </execution>
30     </executions>
31 </plugin>

jacoco可以将单元测试的结果,直接生成html网页,分析代码覆盖率。注意 <outputDirectory>target/jacoco-ut</outputDirectory> 这一行的配置,表示将在target/jacoco-ut目录下生成测试报告。

三、编写单测用例

3.1 约定大于规范

以OrderServiceImpl类为例,如果要对它做单元测试,建议按以下约定:

a. 在test/java下创建一个与OrderServiceImpl同名的package名(注:这样的好处是测试类与原类,处于同1个包,代码可见性相同)

b. 然后在该package下创建OrderServiceImplTest类(注意:一般测试类名的风格为 xxxxTest,在原类名后加Test)

3.2 单元测试模板

参考下面的代码模板:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.cnblogs.yjmyzz.springbootdemo.service.impl;

import com.cnblogs.yjmyzz.springbootdemo.service.UserService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class OrderServiceImplTest {

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
    
    /**
     * 真正要测试的类
     */
    @InjectMocks
    private OrderServiceImpl orderService;

    /**
     * 测试类依赖的其它服务
     */
    @Mock
    private UserService userService;

    /**
     * createOrder成功时的用例
     */
    @Test
    public void testCreateOrderSuccess() {
        //todo
    }

    /**
     * createOrder失败时的用例
     */
    @Test
    public void testCreateOrderFailure() {
        //todo
    }

}

讲解一下:

a. 类上的@RunWith要改成 MockitoJUnitRunner.class,否则mockito不生效

b. 真正需要测试的类,要用@InjectMocks,而不是@Mock(更不能是@Autowired)

-- 原因1:@Autowired是Spring的注解,在mock环境下,根本就没有Spring上下文,当然会注入失败。

-- 原因2:也不能是@Mock,@Mock表示该注入的对象是“虚构”的假对象,里面的方法代码根本不会真正运行,统一返回空对象null,即:被@Mock修饰的对象,在该测试类中,其具体的代码永远无法覆盖到!这也就是失败了单元测试的意义。而@InjectMocks修饰的对象,被测试的方法,才会真正进入执行。

另外,测试服务时,被mock注入的类,应该是具体的服务实现类,即:xxxServiceImpl,而不是服务接口,在mock环境中接口是无法实例化的。

c. 通常一个方法,会有运行成功和运行失败二种情况,建议测试类里,用testXXXSuccess以及testXXXFailure区分开来,看起来比较清晰。

3.3 测试覆盖率

先来看看下单失败的情况:下单前有很多参数校验,先验证下这些参数异常的场景。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int userId = 101;
    
    /**
     * createOrder失败时的用例
     */
    @Test
    public void testCreateOrderWhenFail() {
        try {
            orderService.createOrder(null, 10, userId);
        } catch (Exception e) {
            Assert.assertEquals(true, true);
        }

        try {
            orderService.createOrder("book", null, userId);
        } catch (Exception e) {
            Assert.assertEquals(true, true);
        }

        try {
            orderService.createOrder("book", 0, userId);
        } catch (Exception e) {
            Assert.assertEquals(true, true);
        }

        try {
            orderService.createOrder("book", 50, userId);
        } catch (Exception e) {
            Assert.assertEquals(true, true);
        }
    }

命令行下mvn package 跑一下单元测试,全通过后,会在target/jacoco-ut 目录下生成网页报告

浏览器打开index.html,就能看到覆盖率

可以看到,中间那个带部分绿色的,就是我们刚才写过单测的pacakge,一层层点下去,能看到OrderServiceImpl.createOrder方法的代码覆盖情况,绿色的行表示覆盖到了,红色的表示未覆盖。

讲一个小技巧:有些类,比如DAO/Mytatis层自动生成的DO/Entity,还有一些常量定义等,其实没什么测试的必要,可以排除掉,这样不仅可以提高测试的覆盖率,还能让我们更关注于核心业务类的测试。

排除的方法很简单,可jacoco插件里配置exclude规则即可,参考下面这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<configuration>
    <dataFile>target/jacoco.exec</dataFile>
    <outputDirectory>target/jacoco-ut</outputDirectory>
    <excludes>
        <exclude>
            **/cnblogs/yjmyzz/**/aspect/**,
            **/yjmyzz/**/SampleApplication.class
        </exclude>
    </excludes>
</configuration>

这样就把aspect包下的所有类,以及SampleApplication.class这个特定类给排除在单元测试之外,此时再跑一下mvn package ,对比下重新生成的报告

覆盖率从刚才的26%上升到了61%

3.4 mock返回值

从覆盖率上看,刚才createOrder方法里,最后几行并没有覆盖到,可以再写一个用例

问题来了,报异常了!分析下UserService的queryBalance方法实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public BigDecimal queryBalance(int userId) {
        System.out.println("queryBalance=>userId:" + userId);
        //模拟返回100元余额
        return new BigDecimal(100);
    }

已经写死了返回100元,不应该为Null对象,同时还输出了一行日志,但是从测试结果来看,这个方法并没有真正执行。这也就印证了@Mock修饰的对象,是“假”的,并不会真正执行内部的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void testCreateOrderSuccess() throws Exception {
    BigDecimal balance = BigDecimal.TEN;
    //表示:当userService.queryBalance(userId)执行时,将返回balance变量做为返回值
    when(userService.queryBalance(userId)).thenReturn(balance);
    long orderId = orderService.createOrder("phone", 10, userId);
    Assert.assertEquals(orderId, 1L);
}

把测试代码调整下,改成上面这样,利用when(...).thenReturn(...),表示当xxx方法执行时,将模拟返回yyy对象。这样就mock出了userService的返回值

现在测试就通过了,再看看生成的测试报告,最后几行,也被覆盖到了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
8 条评论
热度
最新
不错不错 跟着操作就行 简单易懂
不错不错 跟着操作就行 简单易懂
回复回复点赞举报
打印中文会乱码,不知道是怎么回事
打印中文会乱码,不知道是怎么回事
回复回复点赞举报
一直想用protobuf这个东西。但是一直没用上。。
一直想用protobuf这个东西。但是一直没用上。。
回复回复点赞举报
感觉这个库缝合了clang和Go的类型
感觉这个库缝合了clang和Go的类型
回复回复点赞举报
学到了,谢谢分享,感谢大佬
学到了,谢谢分享,感谢大佬
回复回复点赞举报
感谢作者又学习到了一个新的知识
感谢作者又学习到了一个新的知识
回复回复点赞举报
牛啊牛啊
牛啊牛啊
11点赞举报
/抱拳
/抱拳
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
搞定Protocol Buffers (上)- 使用篇
因为工作中gRPC使用非常频繁,而gRPC的默认序列化编码采用的也是Protocol Buffers。业界也盛传其效率及其高效:
用户3904122
2022/06/29
5K0
搞定Protocol Buffers (上)- 使用篇
GRPC: Protocol Buffers 3 语法与使用探讨
Protocol Buffers(简称Protobuf)是一种语言中立、平台中立、可扩展的序列化数据结构的方式。它由Google开发,是一种类似于XML和JSON的数据交换格式,但具有更高的效率和灵活性。在本文中,我们将详细探讨Protocol Buffers 3的语法和使用方法。
运维开发王义杰
2024/06/25
2490
GRPC: Protocol Buffers 3 语法与使用探讨
Python 在Python中使用Protocol Buffers基础介绍
https://github.com/protocolbuffers/protobuf/releases
授客
2024/11/21
1410
google protobuf学习笔记:编译安装、序列化、反序列化
简介 protobuf也叫protocol buffer是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 、json进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。 prot
_gongluck
2018/03/09
13.3K1
Go微服务(二)——Protobuf详细入门
Protobuf是Protocol Buffers的简称,它是Google公司开发的⼀种数据描述语⾔,并于2008年对外开 源。Protobuf刚开源时的定位类似于XML、JSON等数据描述语⾔,通过附带⼯具⽣成代码并实现将结 构化数据序列化的功能。但是我们更关注的是Protobuf作为接⼝规范的描述语⾔,可以作为设计安全的 跨语⾔PRC接⼝的基础⼯具。
传说之下的花儿
2023/04/16
3.6K0
Go微服务(二)——Protobuf详细入门
protocol buffer开发指南
ProtoBuf 是一套接口描述语言(IDL)和相关工具集(主要是 protoc,基于 C++ 实现),类似 Apache 的 Thrift)。用户写好 .proto 描述文件,之后使用 protoc 可以很容易编译成众多计算机语言(C++、Java、Python、C#、Golang 等)的接口代码。(摘自:ProtoBuf 与 gRPC 你需要知道的知识)
charlieroro
2020/03/24
8350
Go每日一库之94:protobuf
protobuf 即 Protocol Buffers,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。protobuf 性能和效率大幅度优于 JSON、XML 等其他的结构化数据格式。protobuf 是以二进制方式存储的,占用空间小,但也带来了可读性差的缺点。protobuf 在通信协议和数据存储等领域应用广泛。例如著名的分布式缓存工具 Memcached 的 Go 语言版本groupcache 就使用了 protobuf 作为其 RPC 数据格式。
luckpunk
2023/09/30
6910
ProtoBuf 生成 Go 代码去掉 JSON tag omitempty
我们经常使用 PB(ProtoBuf)作为数据的交换协议,用于数据的序列化与反序列化。对于 PB 生成的 Go strutc,将其序列化为 JSON 时,比如对于数字类型,默认值为零,将不会出现在 JSON 串中。
恋喵大鲤鱼
2022/06/02
5.9K0
ProtoBuf 生成 Go 代码去掉 JSON tag omitempty
protobuf 序列化和反序列化
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程,与之相对应的过程称之为反序列化(Unserialization)。序列化和反序列化主要用于解决在跨平台和跨语言的情况下, 模块之间的交互和调用,但其本质是为了解决数据传输问题。
洁洁
2024/03/15
6610
深入protobuf(Protocol Buffers)原理:简化你的数据序列化
Protocol buffers 是⼀种语⾔中⽴,平台⽆关,可扩展的序列化数据的格式,可⽤于通信协议,数据存储 等。Protocol buffers 在序列化数据具有灵活、⾼效的特点。
Lion Long
2024/11/04
3.8K0
深入protobuf(Protocol Buffers)原理:简化你的数据序列化
在Go中使用Protobuf
本教程使用proto3版本的protocol buffer语言,提供了一个基本的在Go程序中使用protocol buffer的介绍。通过创建一个简单的示例应用程序,向你展示如何
KevinYan
2019/10/13
1.4K0
Google 序列化神器 Protocol Buffer 学习指南
在现代软件开发中,数据的高效传输和存储是一个关键问题。Google 开发的 Protocol Buffer(简称 Protobuf)作为一种语言中立、平台无关、可扩展的机制,用于高效地序列化结构化数据。它比 XML 或 JSON 更加紧凑和高效,非常适合需要高性能和小体积的场景。
Michel_Rolle
2024/06/17
3.3K0
Go with Protobuf
本教程使用proto3向 Go 程序员介绍如何使用 protobuf。通过创建一个简单的示例应用程序,它向你展示了如何:
孟斯特
2023/10/08
2400
Go with Protobuf
Go Protobuf(比xml小3-10倍, 快20-100倍)
protocol buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小、更快、更为简单。你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏根据旧数据结构编译而成并且已部署的程序。
iginkgo18
2021/04/13
2.1K0
强大的序列化工具:Protocol Buffers
Protocol Buffers 为结构化数据的序列化向前兼容,向后兼容,提供了语言中立、平台无关、可扩展机制的途径。类似JSON,但比JSON更小、更快。
Yuyy
2022/09/21
2.1K0
嵌入式linux之go语言开发(七)protobuf的使用
之前写过一篇博文:《如果终端采用protobuf与采集前置通信,能带来哪些变革?https://blog.csdn.net/yyz_1987/article/details/81147454》,介绍了使用protobuf作为序列化通信格式的诸多好处。
杨永贞
2020/08/04
1.1K0
【protobuf】三、proto3语法详解② -- enum、Any、oneof、map类型
​ 将两个“具有相同枚举值名称”的枚举类型放在单个 .proto 文件下测试时,编译后会报错:“某某某常量已经被定义”,所以这里要注意:
利刃大大
2025/02/03
4450
【protobuf】三、proto3语法详解② -- enum、Any、oneof、map类型
protoc语法详解及结合grpc定义服务
说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性。但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择。而今天总结的Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。本文主要以Golang语言进行介绍。
陌无崖
2019/08/16
2.8K0
protoc语法详解及结合grpc定义服务
Golang语言下使用Protocol Buffer教程
Protobuf是Google旗下的一款平台无关,语言无关,可扩展的序列化结构数据格式。所以很适合用做数据存储和作为不同应用,不同语言之间相互通信的数据交换格式,只要实现相同的协议格式即同一proto文件被编译成不同的语言版本,加入到各自的工程中去,这样不同语言就可以解析其他语言通过Protobuf序列化的数据。目前官网提供了C++,Python,JAVA,GO等语言的支持。
Zoctopus
2018/08/28
1.2K0
Golang语言下使用Protocol Buffer教程
搞定Protocol Buffers (下)- 原来你是这样的pb
上图是从官网找的一个protocol buffers的序列化压测对比图,从图上来看protocol buffers表现相对还是比较优异的。
用户3904122
2022/06/29
1.2K0
搞定Protocol Buffers (下)- 原来你是这样的pb
推荐阅读
相关推荐
搞定Protocol Buffers (上)- 使用篇
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验