单元测试的思考与实践

1. 什么是单元测试

  通常来说单元测试,是一种自动化测试,同时包含一下特性:

  ·验证很小的一段代码(业务意义 或者 代码逻辑 上不可再分割的单元),能够更准确的定位到问题代码的位置

  · 能够快速运行(单元测试的意义,在于快速且周期性的验证原有代码的准确性),提高项目开发效率

  · 以隔离的方式 (isolated manner)运行(对外部依赖通过插桩解耦,避免单元测试的复杂度,实现问题快速定位,简化单元测试的运行环境,多个单元测试可以以任何顺序甚至并行进行)

  2. 为什么要单元测试

  因为单元测试有如下优点:

  · 能快速的回归,提高自测的效率

  · 集成测试或者端到端的手工测试效率低,而且无法覆盖到更细节的逻辑分支

  · 也存在功能设计超前于产品设计,通过接口维度,无法触达某些逻辑分支,需要通过单元测试来覆盖

  · 功能开发人员更了解代码的实现,开发人员写出的测试用例往往能更全面的覆盖代码

  · 有良好单测的代码,往往更方便重构

  · 单元测试是项目代码的一部分,维护方便,当然这也依赖良好的单元测试编写习惯,合适的颗粒度

  3. 如何识别有测试价值的代码?

  当我们考虑给代码添加 单元测试时,需要首先考虑加入单测后能够带来的收益有多少,以及其付出的成本有多少,用最小的维护成本提供最高的价值的单元测试。

  3.1 项目属性

  软件本身发布更新成本比较大,如嵌入式软件,客户端程序;或者 软件的缺陷 更可能带来较大的资损,如工厂,银行内部的软件,这类软件都是需要优先考虑单元测试。

  如果一个项目本身不是特别核心的项目,影响面小,迭代更新相对较容易,那么对单元测试的要求,或者说对质量的要求,也就没有那么强烈。

  3.2 代码属性

  3.2.1 重要的代码

  · 领域层

  · 基础设施代码

  3.2.2 不容易被集成测试覆盖的代码

  · 边界条件

  · 异常条件

  · 低概率场景

  3.2.3 容易出现问题的代码

  · 复杂的业务逻辑分支

  · 状态机

  · 胶水代码:负责组合多个功能,多个功能的输入具有不确定性

  3.3 个人不建议的单元测试的行为

  通常来说不建议在单元测试的时候,启动spring容器后,会牵扯过多的外部依赖,导致单元测试难以进行,或者成本过高。

  同样,外部接口,数据库依赖,中间件依赖,都不建议在单元测试中加载,可以通过mock或者sub的方式来进行隔离。

  4. 编写 Unit Test

  通常按照单元测试的AAA模式来编写单元测试,分为三部分:Arrange, Act, Assert

  1)Arrange

  准备测试数据和测试环境,确保测试的可重复性和可预测性。这包括初始化对象、设置变量、模拟外部依赖等

  2)Act

  执行实际的测试操作,也就是调用需要测试的方法或函数,并获取返回值或状态。这个阶段应该仅包含单个操作,以确保测试的独立性和可维护性

  3)Assert

  验证测试结果是否符合预期,也就是检查实际的输出是否与预期的输出相同。如果结果不符合预期,我们需要检查测试代码和被测试代码,找出问题所在并进行修复

  4)结果验证 - 对函数返回结果进行验证

  5)状态验证 - 对过程中的属性值来进行验证

  6)行为验证 - 对过程中会执行的动作进行验证

  spock测试框架代码示例:

class OrderServiceImplTest extends Specification {OrderService orderService = new OrderServiceImpl();InventoryService inventoryService = Mock(InventoryService)OrderConverter orderConverter = Mock(OrderConverter.class)PaymentChannelClient paymentChannelClient = Mock(PaymentChannelClient)OrderMapper orderMapper = Mock(OrderMapper)def setupSpec() {}    // runs once -  before the first feature methoddef setup() { // runs before every feature methodorderService.setInventoryService(inventoryService)orderService.setPaymentChannelClient(paymentChannelClient)orderService.setOrderMapper(orderMapper)orderService.setOrderConverter(orderConverter)}def cleanup() {}      // runs after every feature methoddef cleanupSpec() {}  // runs once -  after the last feature methoddef "create order correctly"() {//准备测试需要的参数given:Long id = 1CreateOrderCommand command = new CreateOrderCommand(orderNo, itemNo, orderItemQuantity, user, totalPrice)//创建一个spy,可以用来做行为验证MockOrderEntity spyOrder = Spy(constructorArgs: [id, orderNo, itemNo, orderItemQuantity, null, user, totalPrice])//指定返回spyorderConverter.toEntity(_ as CreateOrderCommand) >> spyOrderLockInventoryCommand lockInventoryCommand = new LockInventoryCommand(itemNo, orderItemQuantity)when://触发测试Long resultId = orderService.createOrder(command)then://行为验证, 创建订单的同时,执行锁定库存lockInventory会被执行一次,同时会验证参数是否和我们提供lockInventoryCommand是否equals1 * inventoryService.lockInventory(lockInventoryCommand)//行为验证,最终订单执行insert1 * spyOrder.insert()//结果验证,验证返回的idresultId == id//状态验证spyOrder.orderStatus == OrderStatus.CREATE//以表格的形式提供测试数据集合where:orderNo | itemNo | orderItemQuantity | user    | totalPrice"1"     | "it"   | 10                | "userA" | 9.9}}

 

5. 如何自动化执行单元测试

  使用spock框架进行单测,可以通过添加maven插件,来在maven打包的时候自动执行单元测试代码。

<dependency><groupId>org.spockframework</groupId><artifactId>spock-core</artifactId><version>2.1-groovy-3.0</version><scope>test</scope></dependency><dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.10.19</version><scope>test</scope></dependency><!-- Mandatory plugins for using Spock --><plugin><groupId>org.codehaus.gmavenplus</groupId><artifactId>gmavenplus-plugin</artifactId><version>1.12.0</version><executions><execution><goals><goal>compile</goal><goal>compileTests</goal></goals></execution></executions></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>3.0.0-M5</version><configuration><includes><!-- 指定后缀为Test的文件,需要被执行单元测试 --><include>**/*Test</include></includes></configuration></plugin>

 

6. Spock测试框架中Mock,Stub,Spy的区别

  Stub(桩对象):Stub对象用于模拟被测试对象的某些行为。Stub对象通常用来模拟一些外部依赖(interface)返回指定数据,以便于进行单元测试。不能用于用来做行为验证。

def ""() {given:def inventoryMapper = Stub(InventoryMapper)InventoryServce inventoryService = new InventoryServiceImpl(inventoryMapper)Inventory inv = new Inventory(10)inventoryMapper.selectById(_) >> invwhen://inventoryService.stockOut(quantity, id)inventoryService.stockOut(5, 1)then:inv.quantity == 5}

Mock(模拟对象):Mock对象和Stub对象类似,但是可以用来做行为验证,所以在spock中通常可以用mock替代stub。

def ""() {given:def inventoryMapper = Mock(InventoryMapper)InventoryServce inventoryService = new InventoryServiceImpl(inventoryMapper)Inventory inv = new Inventory(10)inventoryMapper.selectById(_) >> invwhen://inventoryService.stockOut(quantity, id)inventoryService.stockOut(5, 1)then://行为验证,inventoryMapper执行了一次stockOut1 * inventoryMapper.stockOut(_)inv.quantity == 5}

3. Spy(监视对象):上面的Stub,Mock都是创建一个假的实例,而Spy是在真实实例的基础上,类似创建一个包装类,它可以记录被测试对象的行为。既保留了原有实例功能的同时,还可以做行为验证。

```groovydef ""() {given:def inventoryMapper = Stub(InventoryMapper)InventoryServce inventoryService = new InventoryServiceImpl(inventoryMapper)Inventory inv = Spy(Inventory)inv.setQuantity(10)inventoryMapper.selectById(_) >> invwhen://inventoryService.stockOut(quantity, id)inventoryService.stockOut(5, 1)then://行为验证,inv执行了一次stockOut1 * inv.stockOut(_)inv.quantity == 5}

通常来说调用Spy对象的方法,会被默认委托给真实的对象来执行,即执行真实的方法,但是Spy同样也适用Stub行为,如:

 def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])//Spy对象也可以像 Stub对象一样,替换掉receive方法,返回指定的值subscriber.receive(_) >> "ok"

7. Partial Mocks(部分Mock)

  7.1 callRealMethod

  通常来说Mock可以对class或者interface创建一个fake对象,不会执行真实的方法,当在写单元测试时有时会需要执行Mock对象的某些真实方法的时候,可以callRealMethod的方式来执行。

 given:def subscriber = Mock(SubscriberImpl)//mock call方法subscriber.call(_) >> {return "called"}//通过callRealMethod指定mock对象执行原来的真实方法subscriber.receive(_) >> { callRealMethod() }then:subscriber.receive("")

7.2 spy

  通过callRealMethod是一种方式,另一种,就是通过Spy来实现,因为Spy是基于真实的对象创建的,那么就可以反过来实现一个对象既可以调用真实方法,又可以调用假的方法。

given:def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])//mock call方法subscriber.call(_) >> {return "called"}then://这里会直接执行真实方法subscriber.receive("")

感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

 

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取   

 

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

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

相关文章

opencv中文路径问题

目的 在windows系统上&#xff0c;就是直接用QT的utf8编码作为图片路径用在opencv读取或者写入函数&#xff0c;在路径当中含有中文时&#xff0c;会提示编码错误。 就是解决opencv中的中文路径的问题。 情况 代码如下&#xff1a; #pragma execution_character_set("…

以CMDB为基础构建DevOps平台体系

在当今数字化转型的浪潮中&#xff0c;企业IT运维模式正从传统的资产管理向现代化的资源管理转变。配置管理数据库&#xff08;CMDB&#xff09;作为IT运维的核心组成部分&#xff0c;其在DevOps平台中的重要性愈加凸显。通过国信证券和招商银行的实际案例&#xff0c;我们将详…

css 文字下划线 text-decoration

背景&#xff1a; 在某些时候需要给文字添加特殊样式。使用 text-decoration: underline; 来为段落文字添加下划线。也有其它文本装饰样式&#xff0c;例如&#xff1a; none&#xff1a;无装饰&#xff08;去掉装饰&#xff09;overline&#xff1a;上划线line-through&…

《珊瑚岛》是一款什么类型的游戏 苹果电脑如何玩到《珊瑚岛》

在众多电子游戏中&#xff0c;有些游戏因其独特的游戏体验和丰富的内容而脱颖而出&#xff0c;《珊瑚岛》便是其中之一。在游戏中你将离开宝京前往珊瑚岛&#xff0c;种植农作物、饲养动物、和岛民成为朋友。您不仅可以振兴该岛小镇&#xff0c;还可以保护和修复周围的珊瑚礁。…

C# OpenCV 部署RecRecNet广角图像畸变矫正

C# OpenCV 部署RecRecNet广角图像畸变矫正 目录 说明 效果 模型信息 项目 代码 下载 说明 ICCV2023 - RecRecNet: Rectangling Rectified Wide-Angle Images by Thin-Plate Spline Model and DoF-based Curriculum Learning 参考&#xff1a; https://github.com/Kang…

CleanMyMac中文版2024破解完美版本下载链接

CleanMyMac中文版&#xff0c;是一款功能强大的系统优化软件。它能够帮助你清理垃圾文件、卸载无用应用、优化内存使用等&#xff0c;让你的电脑运行更加流畅稳定。 CleanMyMac中文版具有智能扫描功能&#xff0c;能够自动识别电脑上的垃圾文件和冗余数据。它能够快速扫描整个…

【MongoDB 新搭档 Kafka】

对于做过数据处理&#xff0c;使用过消息队列的小伙伴 &#xff0c;Kafka可以算是老朋友了&#xff0c;但是最近一个场景下&#xff0c;新的用法&#xff0c;让其变为了MongoDB的新搭档。 开始 从一个问题开始&#xff0c;熟悉MongoDB的小伙伴&#xff0c;可能使用过changeSt…

Java的三个接口Comparable,Comparator,Cloneable(浅拷贝与深拷贝)

Comparable 当我们要进行对象的比较的时候&#xff0c;我们是不能直接用>、< 这些符号直接进行比较的。 由于这是引用类型变量也是自定义类型变量&#xff0c;直接进行比较的时候&#xff0c;我们是通过对象的地址进行比较的&#xff0c;我们可以使用、! 进行两个对象的…

Spring Cloud 专题-前言篇(1)

引言 随着微服务架构的兴起&#xff0c;Spring Cloud 作为一套基于 Spring Boot 实现的云应用开发工具集&#xff0c;为开发者提供了在分布式系统&#xff08;如配置管理、服务发现、断路器、智能路由、微代理、控制总线等&#xff09;中快速构建一些常见模式的能力。本篇文档…

2024年大韩民国最佳品牌大赏 彭雨凡荣获“海外邀请特别奖”

14日&#xff0c;“2024年大韩民国最佳品牌大赏-韩流演艺大赏”颁奖典礼在韩国首尔永登浦区汝矣岛洞国会议员会馆第2会议室举办。 演员彭雨凡荣获“海外邀请特别奖”。 据悉&#xff0c;由大韩民国最佳品牌协会和世宗大王国民委员会&#xff08;理事长 LEE YUNTAE&#xff09…

关于IOMMU问题的扩展

关联CSDN&#xff1a; Steam Deck OLED WLAN下载速率过低问题的排查和解决-CSDN博客 前言 如前所述&#xff0c;Steam Deck OLED WLAN速率低问题和IOMMU有一定的关系&#xff0c;这里我们对IOMMU为什么会对速率有影响进行一个较深入的理解。 对于IOMMU我相信大家通过网上的…

Android中的Audio系统框架分析(一)

概述 Audio系统是Android 平台重要的组成部分&#xff0c;我们将从以下几个方面来讲解&#xff1a; 一Audio基础知识讲解 二、Android系统中Audio框架 Audio基础知识讲解 我们大家知道声音是由物体振动产生的声波。是通过介质&#xff08;空气或固体、液体&#xff09;传播并…

CrossOver Games For Mac官方下载_2024电脑最新版软件安装包下载

CrossOver Pro For Mac是由codewaver公司开发的类虚拟机软件&#xff0c;目的是使linux和Mac OS X操作系统和window系统兼容。CrossOver Pro For Mac能够直接在Mac上运行Windows软件与游戏&#xff0c;而不需虚拟机&#xff0c;功能是非常强大的&#xff0c;值得大家下载使用。…

在Spring Boot中使用Sa-Token实现路径拦截和特定接口放行

在Spring Boot中使用Sa-Token实现路径拦截和特定接口放行 很喜欢的一段话&#xff1a;别想太多&#xff0c;好好生活&#xff0c;也许日子过着过着就会有答案&#xff0c;努力走着走着就会有温柔的着落。 春在路上&#xff0c;花在枝上&#xff0c;所有的美好都在路上&#xff…

【测试专题】系统测试报告(原件Word)

软件测试报告在软件开发过程中起着至关重要的作用&#xff0c;主要有以下几个主要原因&#xff1a; 1、确保软件质量 2、提供决策支持 3、记录测试过程和结果 4、促进沟通和协作 5、符合标准和法规要求 6、改进测试流程和策略 7、降低风险 软件开发全套资料获取进主页或者本文末…

IO流(二)

IO流&#xff08;二&#xff09; 目录 IO流 —— 字符流IO流 —— 缓冲流IO流 —— 转换流IO流 —— 打印流IO流 —— 数据流IO流 —— 序列化流 1.IO流 —— 字符流 文件字符输入流 —— 读字符数据进来 字节流&#xff1a;适合复制文件等&#xff0c;不适合读写文本文件字…

nginx rewrite地址重写

目录 常用的nginx正则表达式 location和rewrite的区别 一、location 1.location常用匹配类型 2.location匹配机制 3.实际工作中三大匹配规则 1.网站首页匹配 2.网站静态页面&#xff0c;通过前缀匹配或通用匹配在nginx服务器本地处理 3.网站动态页面&#xff0c;通过匹…

PostgreSQL源码分析——initdb

数据库初始化 在安装完数据库后&#xff0c;需要进行初始化数据库操作&#xff0c;对应PostgreSQL数据库中就是需要进行initdb后&#xff0c;才能对数据库进行启动。initdb的过程&#xff0c;其实就是创建数据库实例的过程&#xff0c;生成模板数据库和相应的目录、文件信息&a…

uniapp小程序限制微信群访问(图文教程)

我有一个微信小程序 “程序员实用资源” 我现在只想让我的微信群可以访问这个小程序的所有功能 所以我必须对我小程序的来源进行限制&#xff0c;让部分功能在正常访问的时候提示没有加群&#xff0c;不可访问&#xff0c;只有从群内点击进入小程序的时候才可以访问这部分功能…

目标检测顶会新成果!20个突破性方法,更高性能,更强理解与分析能力!

【目标检测】在近年来的深度学习领域中备受关注&#xff0c;它通过识别和定位图像中的目标对象&#xff0c;提升了模型在图像理解和分析方面的能力。目标检测技术在自动驾驶、安防监控和医疗影像分析等任务中取得了显著成果。其独特的方法和卓越的表现使其成为研究热点之一。 为…