当前位置: 首页 > 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.dtcms.com/a/275978.html

相关文章:

  • 【LeetCode】算法详解#8 ---螺旋矩阵
  • Linux->基础IO
  • 佩戴头盔数据集,5498张图片,平均识别率95.3% 其中戴头盔的图有2348张,支持yolo,coco json, pasical voc xml格式的标注
  • Ansible 入门指南:自动化配置管理核心技术与实战 SELinux 配置
  • day051-ansible循环、判断与jinja2模板
  • Frida绕过SSL Pinning (证书绑定)抓包;Frida注入;app无法抓包问题解决。
  • Spring之【写一个简单的IOC容器EasySpring】
  • 2025年亚太杯(中文赛项)数学建模B题【疾病的预测与大数据分析】原创论文分享
  • UE5多人MOBA+GAS 19、创建升龙技能,以及带力的被动,为升龙技能添加冷却和消耗
  • 3. java 堆和 JVM 内存结构
  • YOLOv8
  • pytables模块安装
  • 【TOOL】ubuntu升级cmake版本
  • 单细胞分析教程 | (二)标准化、特征选择、降为、聚类及可视化
  • STM32用PWM驱动步进电机
  • 快捷跑通ultralytics下的yolo系列
  • 算法第三十一天:贪心算法part05(第八章)
  • 回溯算法-数据结构与算法
  • Pythone第二次作业
  • brpc 介绍与安装
  • Redis过期策略与内存淘汰机制面试笔记
  • 数据库连接池及其核心特点
  • AI编程下的需求规格文档的问题及新规范
  • ADSP-1802这颗ADI的最新DSP应该怎么做开发(一)
  • 【Redis实战】Widnows本地模拟Redis集群的2种方法
  • Syntax Error: TypeError: Cannot set properties of undefined (setting ‘parent‘)
  • Unity URP + XR 自定义 Skybox 在真机变黑问题全解析与解决方案(支持 Pico、Quest 等一体机)
  • Cookie、Session、Token 有什么区别?
  • Spring Boot 中使用 Lombok 进行依赖注入的示例
  • 【离线数仓项目】——电商域DWD层开发实战