当前位置: 首页 > news >正文

Mock 单元测试

作者:小凯
沉淀、分享、成长,让自己和他人都能有所收获!

本文的宗旨在于通过简单干净实践的方式教会读者,如何使用 Mock (opens new window)进行工程的单元测试,以便于验证系统中的独立模块功能的健壮性。

从整个工程所处不同阶段的测试手段包括;单元测试、集成测试、系统测试、验收测试、性能测试、安全测试、回归测试,以及兼容、可靠、可用性测试。

而单元测试的重点在于,对工程开发中的代码,进行流程中的单元化测试。如一整个下单流程中,需要调用各项外部的接口(风控、账户、营销、试算、支付),才能完成整个下单流程。但在本地开发过程中,不太能将所有的外部接口都调试为开发环境可用状态,所有这个时候要做单元化测试,对于一些不能随时提供服务的接口进行 Mock (opens new window)处理。

一、案例背景
因为 Mock 单元测试的重点,主要体现在;功能流程较长、调用外部接口稳定性较差、测试过程中希望可以不启动 SpringBoot 应用就能对单个功能模块进行测试验证。

所以本章节带着这样一个案例背景的情况,这里通过 《HTTP 框架使用和场景实战 - 结合ChatGLM自动回帖!》 (opens new window)做一个小重构。来对 Mock 框架进行验证使用。
在这里插入图片描述

首先,这里使用 DDD 工程模型结构,搭建出测试工程。—— DDD 是一种软件设计方法,而软件的设计方法涵盖了;范式、模型、框架、方法论。所以通常下 MVC 与 DDD 的对比先从模型、框架在到思想设计和方法论。
之后,我们在这样的一个模型结构下,实现出自动回帖的领域功能。而这个模型的实现恰好需要调用外部的接口和 ChatGLM SDK,这与我们要做的 Mock 测试正好符合,因为在大部分开发场景下,远程的 HTTP 调用可能不不会一直可用,所以可以用 Mock 方式进行模拟。
二、功能实现
1. 工程结构
在这里插入图片描述

  • 在 domain 中实现一个zsxq的自动回帖领域,而它所需的要调用的接口则由基础设施层提供。
  • 另外在 app 中还有 ChatGLM SDK 的配置启动,也会被注入到 AiReply 实现类中。

2. Ai模块启动

# ChatGLM SDK Config
chatglm:sdk:config:# 状态;true = 开启、false 关闭enabled: false# 官网地址api-host: https://open.bigmodel.cn/# 官网申请 https://open.bigmodel.cn/usercenter/apikeysapi-secret-key: d570f7c5d289cdac2abdfdc562e39f3f.trqz1dH8ZK6ED7Pg
@Bean
@ConditionalOnProperty(value = "chatglm.sdk.config.enabled", havingValue = "true", matchIfMissing = false)
public OpenAiSession openAiSession(ChatGLMSDKConfigProperties properties) {// 1. 配置文件cn.bugstack.chatglm.session.Configuration configuration = new cn.bugstack.chatglm.session.Configuration();configuration.setApiHost(properties.getApiHost());configuration.setApiSecretKey(properties.getApiSecretKey());// 2. 会话工厂OpenAiSessionFactory factory = new DefaultOpenAiSessionFactory(configuration);// 3. 开启会话return factory.openSession();
}
  • 所有的这些配置类的服务,都可以放到 app下的 config 模块中。
  • ChatGLM 可以直接在官网申请,默认会赠送18元的额度,对于它所提供的模型,还是非常够测试使用的。

3. 基础设置 - 接口调用
3.1 接口 - 防腐对接
源码:cn.bugstack.xfg.dev.tech.infrastructure.gateway.api.IZSXQApi

public interface IZSXQApi {/*** 查询知识星球帖子内容** @return 帖子数据* @throws IOException 异常*/ResponseDTO topics() throws IOException;/*** 回复帖子** @param topicId 帖子ID* @param content 回复内容*/void comment(long topicId, String content);}
  • 在基础设置层中定义 gateway 网关接口调用,对于外部的接口使用,中间要做一层防腐,不要直接把外部的接口暴露出去使用。

3.2 使用 - 依赖倒置
源码:cn.bugstack.xfg.dev.tech.infrastructure.gateway.adapter.ZSXQAdapter

public class ZSXQAdapter implements IZSXQAdapter {@Resourceprivate IZSXQApi zsxqApi;@Overridepublic List<TopicsItemVO> queryTopics() {try {ResponseDTO responseDTO = zsxqApi.topics();RespData respData = responseDTO.getRespData();List<TopicsItem> topics = respData.getTopics();List<TopicsItemVO> topicsItemVOList = new ArrayList<>();for (TopicsItem topicsItem : topics) {TopicsItemVO topicsItemVO = TopicsItemVO.builder().topicId(topicsItem.getTopicId()).talk(topicsItem.getTalk().getText()).showCommentsItems(topicsItem.getShowComments() != null ? topicsItem.getShowComments().stream().map(showCommentsItem -> {TopicsItemVO.ShowCommentsItem item = new TopicsItemVO.ShowCommentsItem();item.setUserId(showCommentsItem.getOwner().getUserId());return item;}).collect(Collectors.toList()) : new ArrayList<>()).build();topicsItemVOList.add(topicsItemVO);}return topicsItemVOList;} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic boolean comment(long topicId, String content) {zsxqApi.comment(topicId, content);return true;}}
  • 注意,TopicsItemVO 对象来自于 domain 下领域中模型下的 VO 对象。因为是依赖倒置的,所以 infrastructure 引用的是 domain 并对其接口做实现处理。
  • 并且,TopicsItemVO 只是需要获取自己需要的对象,还可以做简单的封装处理。这样可以衔接外部接口和内部逻辑中间的桥梁,不做强关联。

4. 任务调度
源码:cn.bugstack.xfg.dev.tech.job.ReplyJob

public class ReplyJob {@Resourceprivate IAiReply aiReply;@Scheduled(cron = "0/10 * * * * ?")public void exec() throws Exception {log.info("自动回帖任务开始执行...");aiReply.doAiReply();}}
  • 现在在 trigger 触发器层中的 job 下,就可以调用我们已经实现好的 AiReply 自动回帖功能了。
  • 此外,注意 Application 中 @EnableScheduling 注解是开启的,否则任务不能执行。

三、系统测试
1. 集成测试

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {@Resourceprivate IAiReply aiReply;@Testpublic void test_IAiReply() {aiReply.doAiReply();}}
  • 通常情况下这种测试是最多的,写多少功能,就直接测试调用。如功能中所用到的;HTTP接口、RPC接口、数据库、Redis等资源,都会需要使用到。有时候也因为这样,所以不好测试。那么单元测试就出现了。

2. 单元测试

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class MockTest {@Resourceprivate IAiReply aiReply;@MockBeanprivate IZSXQAdapter izsxqAdapter;@Testpublic void test_doAiReply() throws InterruptedException, JsonProcessingException {Mockito.when(izsxqAdapter.queryTopics()).thenReturn(new ArrayList<TopicsItemVO>() {{TopicsItemVO topicsItemVO = new TopicsItemVO();topicsItemVO.setTopicId(10001L);topicsItemVO.setTalk("<e type=\"mention\" uid=\"241858242255511\" title=\"%40%E5%B0%8F%E5%82%85%E5%93%A5\" /> 提问 java 冒泡排序");add(topicsItemVO);}});Mockito.when(izsxqAdapter.comment(Mockito.anyLong(), Mockito.anyString())).thenReturn(true);aiReply.doAiReply();// 等待;ChatGLM 异步回复new CountDownLatch(1).await();}}

在这里插入图片描述

  • 在基于使用 SpringBoot 的启动,以及一部分功能需要走真实调用的情况下,另外一部分功能的接口可能没法调用时。可以使用这样的一种 MockBean 的方式进行处理,并对整条链路上调用到的接口方法进行 Mock 处理。`Mockito.when(调用到的接口).thenReturn(返回的结果);
  • 那么现在在测试方法中,做了2个Mock操作,把查询帖子和回复帖子,都给处理掉。也就是有了 Mock 以后,程序调用到这里,就直接走 Mock 里设置的结果信息了。

3. 功能测试

@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class ZSXQAdapterTest {@Mockprivate IZSXQApi mockZsxqApi;@InjectMocksprivate ZSXQAdapter zsxqAdapterUnderTest;@Testpublic void testQueryTopics() throws Exception {// Setupfinal List<TopicsItemVO> expectedResult = Arrays.asList(TopicsItemVO.builder().topicId(0L).talk("talk").showCommentsItems(Arrays.asList(TopicsItemVO.ShowCommentsItem.builder().userId(0L).build())).build());// Configure IZSXQApi.topics(...).final ResponseDTO responseDTO = new ResponseDTO();final RespData respData = new RespData();final TopicsItem topicsItem = new TopicsItem();final ShowCommentsItem showCommentsItem = new ShowCommentsItem();final Owner owner = new Owner();owner.setUserId(0L);showCommentsItem.setOwner(owner);topicsItem.setShowComments(Arrays.asList(showCommentsItem));final Talk talk = new Talk();talk.setText("talk");topicsItem.setTalk(talk);topicsItem.setTopicId(0L);respData.setTopics(Arrays.asList(topicsItem));responseDTO.setRespData(respData);when(mockZsxqApi.topics()).thenReturn(responseDTO);// Run the testfinal List<TopicsItemVO> result = zsxqAdapterUnderTest.queryTopics();// Verify the resultsassertEquals(expectedResult, result);log.info("测试结果:{}", JSON.toJSONString(result));}}

在这里插入图片描述

  • 除了前面两种测试,我们在开发功能的时候,还有场景测试;不启动 SpringBoot 但希望对实现的功能进行测试。
  • 那么这里所体现的就是这样的测试,主要使用;@RunWith(MockitoJUnitRunner.class)、@Mock、@InjectMocks 相当于模拟了一个启动的过程,只不过都是 Mock 的信息。但你可以根据这些信息来调试你的接口。
  • 提示:你可以安装 IDEA Plugin Squaretest 它能自动的帮你生成Mock单元测试。这个插件是收费的,但还好不贵。
http://www.dtcms.com/a/285453.html

相关文章:

  • 如何上传github(解决git的时候输入正确的账号密码,但提示认证失败)
  • 撤销和重做!支持数据驱动的事务机制啦(使用OpenGL+QT开发三维CAD)
  • LatentSync: 一键自动生成对嘴型的视频
  • 医疗人工智能的心电图分析:创新技术与临床应用
  • 【playwright篇】教程(十六)[macOS+playwright相关问题]
  • 【Linux 中断机制:工作队列与中断线程化详解】
  • 5 种可行的方法:如何将 Redmi 联系人备份到 Mac
  • RustDesk 自建中继服务器教程(Mac mini)
  • 深入了解消息队列:从基础到选型
  • 云计算虚拟化层I/O性能优化:SR-IOV与DPDK技术的整合应用
  • 电脑视频常用几种接口
  • 工业互联网六大安全挑战的密码“解法”
  • Sentinel dashboard 添加context-path后无法信息无法上传问题
  • 差分隐私机器学习:通过添加噪声让模型更安全,也更智能
  • 【甲烷数据集】Sentinel-5P 卫星获取的全球甲烷数据集-TROPOMI L2 CH₄
  • 共享模式、社群与开源链动2+1模式AI智能名片S2B2C商城小程序的协同发展研究
  • wx小程序设置沉浸式导航文字高度问题
  • 手撕Spring底层系列之:Bean的生命周期
  • html复习
  • 【世纪龙科技】汽车故障诊断与排除仿真教学软件
  • 工业ESD防静电无尘净化棉签擦拭棒:精密制造领域的清洁守护者!
  • 【样式效果】Vue3实现仿制iOS按钮动态效果
  • 基于K8s ingress灰度发布配置
  • 飞书,正在成为中国AI制造故事的新阵地
  • 微信小程序161~170
  • 30、鸿蒙Harmony Next开发:应用文件上传下载,压缩与解压
  • VUE项目学习笔记 v-for绑定数据,该数据异步获取,同时需要对v-for的DOM节点进行js操作
  • 苍穹外卖项目日记(day12)
  • Linux驱动学习day24(UART子系统)
  • AI产品经理面试宝典第36天:AI+旅游以及行业痛点相关面试题的指导