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.5K10
代码可运行
举报
运行总次数: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 删除。

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

评论
登录后参与评论
1 条评论
热度
最新
大佬, 能分享下你代码吗? 我总是报错 Skipping JaCoCo execution due to missing execution data file. 谷歌了各种办法, 都不行, 这种写法还需要什么前提吗??? 还需要其他配置吗?
大佬, 能分享下你代码吗? 我总是报错 Skipping JaCoCo execution due to missing execution data file. 谷歌了各种办法, 都不行, 这种写法还需要什么前提吗??? 还需要其他配置吗?
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
教你使用 Jacoco 统计服务端代码覆盖率
前面有一篇 文章 使用 Python + Coverage 来统计测试用例的代码覆盖率
AirPython
2020/07/29
3.4K1
教你使用 Jacoco 统计服务端代码覆盖率
告别加班/解放双手提高单测覆盖率之Java 自动生成单测代码神器推荐
很多公司对分支单测覆盖率会有一定的要求,比如 单测覆盖率要达到 60% 或者 80%才可以发布。
明明如月学长
2022/02/15
6.7K0
告别加班/解放双手提高单测覆盖率之Java 自动生成单测代码神器推荐
在 Spring 中 Mock RestTemplate
如果我们程序中使用了 RestTemplate 进行 HTTP API 调用。通常在编写单元测试时,为了让测试可控,会将 RestTemlate 调用进行 mock,而不是进行真实的 HTTP API 调用。
每周聚焦
2025/04/15
520
在 Spring 中 Mock RestTemplate
JaCoCo代码覆盖率从0到100的入门实践
JaCoCo全称是Java Code Coverage,Java代码覆盖率,广泛运用于各种测试平台对Java代码的全量覆盖率和增量覆盖率进行统计,分析代码行差异,度量单元测试效果。Jacoco也是精准测试的技术实现手段之一。
dongfanger
2021/12/24
2.2K0
JaCoCo代码覆盖率从0到100的入门实践
为什么程序员一定要写单元测试?
大家好,我是鱼皮,很多初学编程的同学都会认为 “程序员的工作只有开发新功能,功能做完了就完事儿”。但其实不然,保证程序的正常运行、提高程序的稳定性和质量也是程序员的核心工作。
程序员鱼皮
2023/10/23
3360
为什么程序员一定要写单元测试?
使用dropwizard(4)-加入测试-jacoco代码覆盖率
前言 dropwizard提供了一个简单的测试框架。这里简单集成并加入jacoco测试。 Demo source https://github.com/Ryan-Miao/l4dropwizard 本文是基于dropwizard入门之上的演进。 确保依赖都是最新的,或者自行解决版本冲突,比如jackson不同版本之间的类有所不同。 加入dropwizard-testing 在dependencies中增加依赖 <dependency> <groupId>io.dropwizard</groupId
Ryan-Miao
2018/03/14
1.4K0
UnitTest:maven中使用Jacoco计算代码覆盖率
jacoco 官网 https://www.eclemma.org/jacoco/
测试邦
2019/08/09
1.5K0
UnitTest:maven中使用Jacoco计算代码覆盖率
使用JaCoCo Maven插件创建代码覆盖率报告
这篇博客文章描述了我们如何使用JaCoCo Maven插件为单元和集成测试创建代码覆盖率报告。
FunTester
2019/11/19
2K0
JAVA实战:如何让单元测试覆盖率达到80%甚至以上
单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证。它是软件测试中的一种基本方法,也是软件开发过程中的一个重要步骤。
你可以叫我老白
2023/03/21
4K1
JAVA实战:如何让单元测试覆盖率达到80%甚至以上
Spring Boot - JaCoCo Code Coverage
JaCoCo(Java Code Coverage)是一个开源的Java代码覆盖率工具,它主要用于评估Java程序的测试完整性。通过跟踪测试过程中执行的代码,JaCoCo能够提供多种覆盖率指标,帮助开发者确保代码的测试质量。这些指标包括指令覆盖、分支覆盖、圈复杂度、行覆盖、方法覆盖和类覆盖。
小小工匠
2024/01/15
6610
Spring Boot - JaCoCo Code Coverage
Java测试框架推荐
java有很多测试类框架, 开发中有很多比如Mokito, powermock, wiremock, cucumber ,但是powermock测试,sonar不认其覆盖率.
CoffeeLand
2020/05/17
1.4K0
Mockito模拟进行单元测试
    MOCK意思是模拟的意思,主要被用来进行数据的人工组织,不会真正地调用第三方服务器,类似redis,mysql等都不会调用,也不用关心数据底层是如何进行处理的,我们要做的只是将本单元的逻辑进行单元测试,验证数据的逻辑处理性,而其中mock较好的框架就是Mockito。
chinotan
2019/07/15
9.6K0
Mockito模拟进行单元测试
Spock单元测试框架以及在美团优选的实践
Spock是国外一款优秀的测试框架,基于BDD(行为驱动开发)思想实现,功能非常强大。Spock结合Groovy动态语言的特点,提供了各种标签,并采用简单、通用、结构化的描述语言,让编写测试代码更加简洁、高效。目前,美团优选物流绝大部分后端服务已经采用了Spock作为测试框架,在开发效率、可读性和维护性方面均取得了不错的收益。
测试开发社区
2021/08/23
3.4K0
Spock单元测试框架以及在美团优选的实践
覆盖率检查工具:JaCoCo 食用指南
我们今天简单介绍 JaCoCo 的实际使用示例,它是目前在大多数 Java 项目中应用最广泛的覆盖率检测框架
phoenix.xiao
2022/11/11
1.3K0
覆盖率检查工具:JaCoCo 食用指南
SpringBoot - 单元测试利器Mockito入门
Mock 测试就是在测试过程中,创建一个假的对象,避免你为了测试一个方法,却要自行构建整个 Bean 的依赖链。
小小工匠
2021/08/17
17.4K1
SpringBoot - 单元测试利器Mockito入门
jacoco 生成单测覆盖率报告
jacoco 是一个开源的覆盖率工具,它针对的开发语言是 java。其使用方法很灵活,可以嵌入到 ant、maven 中;可以作为 Eclipse 插件;可以作为 javaAgent 探针监控 java 程序等等。
JMCui
2020/05/09
3.4K0
jacoco 生成单测覆盖率报告
TestNG + PowerMock 单元测试
单元测试(Unit Testing),是指对软件或项目中最小可测试单元进行正确性检验的测试工作。单元是人为规定最小可测试的功能模块,可以是一个模块,一个函数或者一个类。单元测试需要与模块开发进行隔离情况下进行测试。
没有故事的陈师傅
2021/05/14
1.8K0
ChatGPT与单元测试
这些测试用例覆盖了Calculator类中的所有方法,并且检查了各种情况下的预期行为。
顾翔
2024/09/10
980
ChatGPT与单元测试
@Spy、@SpyBean、@MockBean、@Mock、@RunWith、@ExtendWith对比
在写单元测试中经常会用到Mockito,但是这些类似的注解非常混乱,今天总结一下相关的注解,说明其中的含义和实现例子。
查拉图斯特拉说
2023/12/19
1.4K0
junit4整合PowerMockito进行单元测试
所以我们在单测中,往往会使用mock的方式对这些代码做一个数据的模拟,从而达到对代码进行测试的一个目的。
半月无霜
2023/10/18
1.3K0
junit4整合PowerMockito进行单元测试
相关推荐
教你使用 Jacoco 统计服务端代码覆盖率
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验