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

小架构step系列12:单元测试

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.lryc.cn/news/585989.html

相关文章:

  • [爬虫实战] 多进程/多线程/协程-异步爬取豆瓣Top250
  • Pytest 跳过测试技巧:灵活控制哪些测试该跑、哪些该跳过
  • linux系统mysql性能优化
  • H2在springboot的单元测试中的应用
  • 多 Agent 强化学习实践指南(一):CTDE PPO 在合作捕食者-猎物游戏中的应用详解
  • 引入了模块但没有使用”,会不会被打包进去
  • 【C++小白逆袭】内存管理从崩溃到精通的秘籍
  • c++反射实现
  • 张量数值计算
  • 跨系统开发代码换行符如何解决
  • 每日一SQL 【销售分析 III】
  • 试用了10款翻译软件后,我只推荐这一款!完全免费还超好用
  • 大模型KV缓存量化误差补偿机制:提升推理效率的关键技术
  • Qt6中出现 OpenCV(4.10.0) Error: Assertion failed
  • 第10讲——一元函数积分学的几何应用
  • 创建 UIKit 项目教程
  • 在 Java 中,计算两个 Integer 类型表示的合格数量与总数量的合格率,并保留四位小数,推荐使用 BigDecimal 来确保精度
  • springboot+swagger2文档从swagger-bootstrap-ui更换为knife4j及文档接口参数不显示问题
  • 股票的k线
  • 从基础加热到智能生态跨越:艾芬达用创新重构行业价值边界!
  • 人工智能自动化编程:传统软件开发vs AI驱动开发对比分析
  • 【科研绘图系列】R语言绘制小提琴图
  • 【TGRS 2025】可变形交互注意力Deform-Interac-Att,即插即用,涨点神器!
  • 【八股消消乐】Kafka集群 full GC 解决方案
  • 系统分析师-计算机系统-输入输出系统
  • 计算机视觉与深度学习 | 基于Matlab的多特征融合可视化指纹识别系统(附完整代码)
  • 3 c++提高——STL常用容器(一)
  • 深度学习图像分类数据集—铜片划痕识别分类
  • RocketMQ-
  • 基于springboot+Vue的二手物品交易的设计与实现