1 概述

测试的种类很多:单元测试、集成测试、系统测试等,程序员写代码进行测试的可以称为白盒测试,单元测试和集成测试都可以进行白盒测试,可以理解为单元测试是对某个类的某个方法进行测试,集成测试则是测试一连串的方法。测试代码也是代码,即使要求测试代码逻辑简单,但写单元测试代码也是要花时间的,维护也是要花时间的,所以在写测试代码上也要有些平衡。单元测试的颗粒度比较小,最接近方法的业务逻辑,应该详尽的测试;而集成测试牵扯的方法比较多,就比较复杂,就挑重点的地方做测试。本文先了解单元测试。

2 单元测试

2.1 业务逻辑测试

在DDD里有一种思想,就是要把业务逻辑分离出来,这部分业务逻辑是业务的核心,应该是公司的核心价值所在,所以应该进行详尽测试。测试要做得多,成本就要低,所以这块的测试主要集中在逻辑方面,业务逻辑的代码也要写成函数的形式,也就是给予一定的参数,就会反馈相同的结果。这块代码不应该跟数据库、中间件等扯上关系,否则就很难做到轻量化。

有不少业务逻辑代码是比较简单的,不一定会按DDD的模式进行文件或者目录分离,但也要在类或者方法层面做到把业务逻辑分离,以便对这些业务进行良好的单元测试。这种测试仅需要对一些类进行mock,然后就跟测试一个有参数有返回值的方法那样简单。这就要求涉及到数据库或者中间件等外部资源的操作,都应该接口化,这样就比较容易进行mock。

2.2 测试例子

假设有个创建组成员的功能,为这个业务逻辑创建一个服务接口GroupMemberCreator和服务类GroupMemberCreatorImpl,里面有个方法create(GroupMember member),用于编写创建成员的业务逻辑,有部分业务会封装到业务对象GroupMember中;这个方法里需要把数据存储到数据库,则通过GroupMemberRepository提供save()接口把数据存储到数据库中,该接口只有数据库相关操作,而没有业务逻辑。

// 把业务逻辑放到GroupMember和GroupMemberCreatorImpl里
public class GroupMember {private Long groupId;private String name;public GroupMember(Long groupId, String name) {// 做业务逻辑:校验参数并组装GroupMember信息this.groupId = groupShouldExist(groupId);this.name = memberNameShouldNotExist(name);}public Long getGroupId() {return groupId;}public String getName() {return name;}private Long groupShouldExist(Long groupId) {// 校验组存在,否则抛异常return groupId;}private String memberNameShouldNotExist(String name) {// 校验成员是否已经存在,否则抛异常return name;}
}public interface GroupMemberCreator {GroupMember create(Long groupId, String memberName);
}
@Service
public class GroupMemberCreatorImpl implements GroupMemberCreator {private GroupMemberRepository repository;@Autowiredpublic GroupMemberCreatorImpl(GroupMemberRepository repository) {this.repository = repository;}@Overridepublic GroupMember create(Long groupId, String memberName) {GroupMember member = new GroupMember(groupId, memberName);return repository.save(member);}
}// Repository只有数据库相关操作,无业务逻辑
public interface GroupMemberRepository {GroupMember save(GroupMember member);
}
@Repository
public class GroupMemberReposistoryImpl implements GroupMemberRepository {@Overridepublic GroupMember save(GroupMember member) {// 调相关DAO操作数据库return member;}
}// 测试用例例子
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.ArgumentMatchers.any;
public class GroupMemberCreatorImplTest {@Testpublic void create_new_member() {Long groupId = 1L;String name = "Joy";// 1. mock对象// 调repository.save()的时候,返回一个mock对象,不真正调用数据库GroupMember mockMember = new GroupMember(groupId, name);GroupMemberRepository repository = Mockito.mock(GroupMemberRepository.class);Mockito.when(repository.save(any())).thenReturn(mockMember);// 2. 测试代码逻辑GroupMemberCreatorImpl creator = new GroupMemberCreatorImpl(repository);GroupMember member = creator.create(groupId, name);// 3. 断言(Assertion)Assertions.assertThat(member).isNotNull();Assertions.assertThat(member.getGroupId()).isEqualTo(groupId);Assertions.assertThat(member.getName()).isEqualTo(name);}
}

从上面测试用例来看,测试的三个步骤:一是用mockito进行mock对象,指定mock对象执行方法的返回值(可根据参数返回);二是调业务逻辑代码进行测试;三是对测试结果进行断言。

通过测试可以反过来思考方法应该如何设计,其规则大概是看输入输出,也就是有什么输入就预期响应的输出,要注意的是方法返回值不一定是唯一的输出,如果方法内有数据库等操作,写入数据库的内容也算一个输出,这个时候可以考虑把数据库的输入反馈到方法返回值当中,总之是要验证指定的输入有指定的输出。

注:调save()保存数据严格来说也不算业务,其只与技术有关,可以进一步从业务逻辑中剥离出来。上面主要是方便说明mock一个对象的例子。

2.3 文档

2.3.1 Mockito

Mockito还是需要学习一下具体的用法的,重点可以先了解如何匹配参数,然后如何指定返回值(含抛异常)等,再高级的用法则可以用到再查找文档。

Mockito各个版本的文档列表:https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html

Mockito-4.5.1的详细文档:https://javadoc.io/doc/org.mockito/mockito-core/4.5.1/org/mockito/Mockito.html

2.3.2 AssertJ

AssertJ则通过方法名称就大概了解其用法了,重点可以了解一下在抛异常的场景如何进行断言。

文档:https://assertj.github.io/doc/

3 架构一小步

规范:把业务规则抽离出来,不依赖数据库和中间件进行详细的单元测试。

规范:编写可测试的代码,如果测试代码有逻辑则反向说明代码可测试性不足。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88771.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88771.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88771.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot3-Flowable7初体验

目录简介准备JDKMySQLflowable-ui创建流程图要注意的地方编码依赖和配置控制器实体Flowable任务处理类验证启动程序调用接口本文源码参考简介 Flowable是一个轻量的Java业务流程引擎,用于实现业务流程的管理和自动化。相较于老牌的Activiti做了一些改进和扩展&…

phpMyAdmin:一款经典的MySQL在线管理工具又回来了

phpMyAdmin 是一个免费开源、基于 Web 的 MySQL/MariaDB 数据库管理和开发工具。它提供了一个直观的图形用户界面,使得我们无需精通复杂的 SQL 命令也能执行大多数数据库管理任务。 phpMyAdmin 项目曾经暂停将近两年,不过 2025 年又开始发布新版本了。 …

存储服务一NFS文件存储概述

前言: 网络文件系统(Network File System,NFS)诞生于1984年,由Sun Microsystems首创,旨在解决异构系统间的文件共享需求。作为一种基于客户端-服务器架构的分布式文件协议,NFS允许远程主机通过T…

libimagequant 在 mac 平台编译双架构

在 macOS 上编译 libimagequant 的双架构(aarch64 x86_64)通用二进制库,以下是完整步骤:​​1. 准备 Rust 工具链​​ # 安装两个目标平台 rustup target add aarch64-apple-darwin x86_64-apple-darwin# 确认安装成功 rustup ta…

暑期自学嵌入式——Day01(C语言阶段)

点关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!! 主页: 一位搞嵌入式的 genius-CSDN博客https://blog.csdn.net/m0_73589512?spm1011.2682.3001.5343感悟: 今天我认为最重…

Flutter基础(前端教程⑧-数据模型)

这个示例展示了如何创建数据模型、解析 JSON 数据,以及在 UI 中使用这些数据:import package:flutter/material.dart; import dart:convert;void main() {// 示例:手动创建User对象final user User(id: 1,name: 张三,age: 25,email: zhangsa…

SSRF10 各种限制绕过之30x跳转绕过协议限制

ssrf漏洞在厂商的处理下可能进行一些特殊处理导致我们无法直接利用漏洞 有以下四种: 1.ip地址限制绕过 2.域名限制绕过 3.30x跳转绕过域名限制 4.DNS rebinding绕过内网ip限制 本章我们讲30x跳转绕过域名限制 30x跳转绕过域名限制 之前我们使用ssrf漏洞时可以…

DNS解析过程和nmap端口扫描

目录 DNS解析流程: nmap端口扫描 指定扫描方式 TCP全连接扫描 -sT SYN半连接扫描 -sS -sT和 -sS的区别 Linux提权 利用好谷歌语法查找敏感信息 如果自己搭建了网站文件要放在phpstudy_pro\WWW下。 如果想要使用域名访问网站,需要在phpstudy_pro…

【基于开源大模型(如deepseek)开发应用及其发展趋势的一点思考】

1. 开源大模型技术发展现状1.1 DeepSeek等主流开源大模型的技术特性分析 DeepSeek作为当前最具代表性的开源大模型之一,其技术架构具有多项创新特性。该模型采用混合专家架构(MoE),通过将视觉编码分离为"理解"和"生成"两条路径&…

java8 ConcurrentHashMap 桶级别锁实现机制

Java 8 ConcurrentHashMap 桶级别锁实现机制 Java 8 中的 ConcurrentHashMap 抛弃了分段锁设计,采用了更细粒度的桶级别锁(bucket-level locking)实现,这是其并发性能提升的关键。下面详细解析其实现原理: 1. 基本实现…

Python正则表达式实战指南

一 正则表达式库正则表达式是文本处理中不可或缺的强大工具,Python通过re模块提供了完整的正则表达式支持。本文将详细介绍re模块中最常用的match()、search()和findall()函数,以及贪婪模式与非贪婪模式的区别,帮助读者掌握Python中正则表达式…

使用球体模型模拟相机成像:地面与天空的可见性判断与纹理映射

在传统相机模拟中,地面通常被建模为一个平面(Plane),这在低空场景下是合理的。但在更大视场范围或远距观察时,地球的曲率不可忽视。因此,我们需要将地面模型从平面升级为球体,并基于球面与光线的…

Agent自动化与代码智能

核心问题: 现在很多团队做AI系统有个大毛病:只顾追求“高大上”的新技术(尤其是AI Agent),不管实际业务需不需要。 结果系统搞得又贵、又复杂、还容易出错。大家被“Agent”这个概念搞晕了:到底啥时候用简单…

SQL141 试卷完成数同比2020年的增长率及排名变化

SQL141 试卷完成数同比2020年的增长率及排名变化 withtemp as (selectexam_id,tag,date(submit_time) as submit_timefromexamination_infoleft join exam_record using (exam_id)wheresubmit_time is not null),2021_temp as (selecttag,count(*) as exam_cnt_21,rank() over…

C语言<数据结构-单链表>

链表是一种常见且重要的数据结构,在 C 语言中,它通过指针将一系列的节点连接起来,每个节点可以存储不同类型的数据。相比数组,链表在插入和删除元素时不需要移动大量数据,具有更好的灵活性,尤其适合处理动态…

archive/tar: unknown file mode ?rwxr-xr-x

这个是我在docker build报错的,这是一个node.js项目。我猜你也是一个node.js下的项目,或者前端项目。 解决方法: .dockerignore里面写一下node_modules就行了。 未能解决:archive/tar:未知文件模式?rwxr-…

【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库

文章目录背景界面当前支持的 Markdown 语法不支持的Markdown 语法代码节选背景 出于兴趣,我使用js实现了一个 markdown语法 -> ast语法树 -> html富文本的库, 其速度应当慢于正则实现的同类js库, 但是语法扩展性更好, 嵌套列表处理起来更方便. 界面 基于此js实现vue组…

【echarts踩坑记录】为什么第二个Y轴最大值不整洁

目录问题复现示意图:解决方法有以下几种:1. 在y轴配置中手动设置max属性:2. 使用ECharts提供的坐标轴标签格式化功能:🚀写在最后问题复现示意图: 今天在用echarts图表的时候,出现了一个小问题。…

Duplicate cleaner pro 的使用技巧

Duplicate cleaner pro 的使用技巧前言文件去重基本介绍经验之谈目录结构修改盘符起因方法手动分配方法‌数据修改方法安装sqlite-web修改数据库GPU加速安装驱动获取驱动和硬件信息安装CUDA配置环境变量&#xff08;如果是自定义安装&#xff09;创建程序<1>获取参数和命…

数字孪生技术引领UI前端设计新趋势:增强现实与虚拟现实的融合应用

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩!一、引言&#xff1a;AR 与 VR 的 “割裂” 与数字孪生的 “融合” 契机增强现实&#xff08;AR&…