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); //
    }
}

总结

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

相关推荐
神奇的程序员19 小时前
从已损坏的备份中拯救数据
运维·后端·前端工程化
oden20 小时前
AI服务商切换太麻烦?一个AI Gateway搞定监控、缓存和故障转移(成本降40%)
后端·openai·api
李慕婉学姐21 小时前
【开题答辩过程】以《基于Android的出租车运行监测系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·后端·vue
m0_7400437321 小时前
SpringBoot05-配置文件-热加载/日志框架slf4j/接口文档工具Swagger/Knife4j
java·spring boot·后端·log4j
招风的黑耳1 天前
我用SpringBoot撸了一个智慧水务监控平台
java·spring boot·后端
Miss_Chenzr1 天前
Springboot优卖电商系统s7zmj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
期待のcode1 天前
Springboot核心构建插件
java·spring boot·后端
2501_921649491 天前
如何获取美股实时行情:Python 量化交易指南
开发语言·后端·python·websocket·金融
CeshirenTester1 天前
Playwright元素定位详解:8种定位策略实战指南
人工智能·功能测试·程序人生·单元测试·自动化
serendipity_hky1 天前
【SpringCloud | 第5篇】Seata分布式事务
分布式·后端·spring·spring cloud·seata·openfeign