springboot集成mockito和jacoco实践

mockito,一款 spring 框架开发中常用的单元测试工具,junit亦然。在实践中二者经常混合使用,各司其职,共同完成测试任务。核心关系和分工如下:

工具 职责 在之前代码中的体现
JUnit 提供测试框架,管理测试生命周期(如 @Test@BeforeEach),断言验证(如 assertEquals)。 1. 标记测试方法 (@Test) 2. 断言结果 (assertEquals) 3. 测试类的结构(如 class XxxTest)。
Mockito 模拟依赖对象(如 Service、DAO),验证交互行为(如 verify)。 1. 模拟 demoService (@Mock) 2. 验证方法调用 (verify(demoService).getDto())。

通常单元测试做完后还需要 "单测覆盖率" 的数据,类似在校时各类大小考试后的卷面评分,这个环节一般使用 jacoco。大型项目可能会使用 SonarQube (听说的,实际有没有我也不知道),这个不在此次讨论范围内。

本文开发版本前提:

JDK spring-boot spring-cloud spring-cloud-alibaba
1.8 2.7.18 2021.0.9 2021.0.6.1

mockito 和 junit 依赖

官网:site.mockito.org/

微服务开发下的mockito已经集成在了spring-boot-start-test模块里,包括junit,因此不需要额外引入。

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>3.5.0</version>
</dependency>

jacoco依赖

jacoco是用来计算代码覆盖率的,小型项目里常用的框架,如果是大规模项目就得用别的了,比如 SonarQube/SonarCloud。

xml 复制代码
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version> <!-- 使用最新版本 -->
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal> <!-- 在测试阶段注入 JaCoCo 代理 -->
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>test</phase> <!-- 在 test 阶段生成报告 -->
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

安装完后用 mvn clean test 在项目根目录下执行就能得到测试报告(前提是已经有单元测试类及代码)

如果要单独指定某一个子模块并屏蔽其他模块,可以用mvn clean test -pl module-name -am。最终生成的报告在目标模块下的 target/site/jacoco 文件夹里。

单元测试与集成测试

如果不想钻牛角尖的话,单元测试可以理解为多程序逻辑中各个"最小单元"的测试,这些最小单元可以被预设输入值和期望输出值,可以验证,这也就是 mockito 要做的事。

之所以说"如果不想钻牛角尖",是因为我自己就思路打结过,毕竟单元可大可小,各功能之间又是调用来调用去的......

单元测试示例

单元测试与集成测试最直观的区别,在我看来就是类层面的注解。

java 复制代码
//单元测试
@ExtendWith(MockitoExtension.class)
//集成测试
@ExtendWith(SpringExtension.class)
@SpringBootTest

正如前面表格所述,mockito 的作用是模拟依赖对象(如 Service、DAO),验证交互行为(如 verify),因此测试时,基本都是要假定输入与输出,再去执行。

以 controller 层的测试为例,测试的是 controller 的接口,但由于 controller 大多数时候调用了 service,所以还需要一个虚拟的 service,用来模拟不同的 service 返回结果在 controller 是否得到了正确处理。

简单写一个用于校验的 service,有多简单?直接返回 Boolean 结果。

java 复制代码
@Service
public class DemoServiceImpl implements DemoService {
    
    @Override
    public boolean jiaoyanTest(){
        return true;
    }

controller 里调用它,并对不同的返回结果做处理------打印不同字符串。

java 复制代码
@RestController
public class DemoController {

    @Autowired
    private DemoService demoService;

    @GetMapping(PREFIX+"/jianquan")
    public String jianquan(){
        String res = "通过";
        if (!demoService.jiaoyanTest()){
            res = "不通过";
        }
        System.out.println("jianquan执行结果:"+res);
        return res;
    }

}

在单元测试类里,就需要这么测试它。

java 复制代码
@Mock
private DemoService demoService;
@InjectMocks
private DemoController demoController;
@Test
void test(){
    //模拟service返回true
    Mockito.when(demoService.jiaoyanTest()).thenReturn(true);
    String res = demoController.jianquan();
    assertEquals("通过",res);
    //模拟service返回false
    Mockito.when(demoService.jiaoyanTest()).thenReturn(false);
    res = demoController.jianquan();
    assertEquals("不通过",res);
}

执行结果就会是两个 assert 断言都正常通过------输入依照设定,输出符合预期,有什么理由不通过呢?

console 复制代码
jianquan执行结果:通过
jianquan执行结果:不通过

如果颠倒断言的顺序而保持 mock 设定的顺序不变,这个逻辑必然出错。

console 复制代码
jianquan执行结果:通过

org.opentest4j.AssertionFailedError: 
Expected :不通过
Actual   :通过
<Click to see difference>

执行结果也忠实地打印了实际效果:验证不通过,并且不再执行下一步校验(毕竟是故意写的错误逻辑)。第一行是 controller 内的打印结果,后面是断言的提示:期望返回应是"不通过",但实际是"通过",说明执行结果与期望不符。

集成测试示例

和平常启动项目时的写法基本一致。

less 复制代码
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class DemoControllerTest {
    @Autowired
    private DemoController demoController;

    @Test
    void test4(){
        // 调用真实的服务方法
        String res = demoController.jianquan();
        // 根据实际业务逻辑验证结果
        assertEquals("不通过", res); //
    }
}

总结

主要方便自己忘了的时候找回写法(。

相关推荐
@昵称不存在1 小时前
Flask input 和datalist结合
后端·python·flask
zhuyasen1 小时前
Go 分布式任务和定时任务太难?sasynq 让异步任务从未如此简单
后端·go
东林牧之2 小时前
Django+celery异步:拿来即用,可移植性高
后端·python·django
超浪的晨3 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
AntBlack3 小时前
从小不学好 ,影刀 + ddddocr 实现图片验证码认证自动化
后端·python·计算机视觉
Pomelo_刘金3 小时前
Clean Architecture 整洁架构:借一只闹钟讲明白「整洁架构」的来龙去脉
后端·架构·rust
双力臂4043 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
midsummer_woo5 小时前
基于spring boot的医院挂号就诊系统(源码+论文)
java·spring boot·后端
Olrookie6 小时前
若依前后端分离版学习笔记(三)——表结构介绍
笔记·后端·mysql
沸腾_罗强6 小时前
Bugs
后端