单元测试的思考与实践

news2024/12/25 11:19:47

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 method
      def setup() { // runs before every feature method
          orderService.setInventoryService(inventoryService)
          orderService.setPaymentChannelClient(paymentChannelClient)
          orderService.setOrderMapper(orderMapper)
          orderService.setOrderConverter(orderConverter)
      }
      def cleanup() {}      // runs after every feature method
      def cleanupSpec() {}  // runs once -  after the last feature method
      def "create order correctly"() {
      
          //准备测试需要的参数
          given:
          Long id = 1
          CreateOrderCommand command = new CreateOrderCommand(orderNo, itemNo, orderItemQuantity, user, totalPrice)
      
          //创建一个spy,可以用来做行为验证
          MockOrderEntity spyOrder = Spy(constructorArgs: [id, orderNo, itemNo, orderItemQuantity, null, user, totalPrice])
      
          //指定返回spy
          orderConverter.toEntity(_ as CreateOrderCommand) >> spyOrder
      
          LockInventoryCommand lockInventoryCommand = new LockInventoryCommand(itemNo, orderItemQuantity)
      
          when:
          //触发测试
          Long resultId = orderService.createOrder(command)
      
          then:
          //行为验证, 创建订单的同时,执行锁定库存lockInventory会被执行一次,同时会验证参数是否和我们提供lockInventoryCommand是否equals
          1 * inventoryService.lockInventory(lockInventoryCommand)
          //行为验证,最终订单执行insert
          1 * spyOrder.insert()
          //结果验证,验证返回的id
          resultId == 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(_) >> inv
      when:
      //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(_) >> inv
      when:
      //inventoryService.stockOut(quantity, id)
      inventoryService.stockOut(5, 1)
      then:
      //行为验证,inventoryMapper执行了一次stockOut
      1 * inventoryMapper.stockOut(_)
      inv.quantity == 5
      
  }

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

```groovy
  def ""() {
      given:
      def inventoryMapper = Stub(InventoryMapper)
      InventoryServce inventoryService = new InventoryServiceImpl(inventoryMapper)
      Inventory inv = Spy(Inventory)
      inv.setQuantity(10)
      inventoryMapper.selectById(_) >> inv
      when:
      //inventoryService.stockOut(quantity, id)
      inventoryService.stockOut(5, 1)
      then:
      //行为验证,inv执行了一次stockOut
      1 * 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.coloradmin.cn/o/1834775.html

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

相关文章

初始化一个Android项目时,Android Studio会自动生成一些文件和目录结构,以帮助你快速上手开发

当你初始化一个Android项目时&#xff0c;Android Studio会自动生成一些文件和目录结构&#xff0c;以帮助你快速上手开发。这些文件和目录各自有其特定的功能和用途。下面我为你解释一下这些自动生成的内容&#xff1a; 1. app 目录 这是你的应用模块的根目录&#xff0c;包…

C++之模板(三)

1、缺省模板参数 可以将数据结构类型传递进来&#xff0c;比如vectop<T>&#xff08;如果没传就是默认&#xff09; 把vector当作类型参数来传递&#xff0c;从而使用它的接口然后适配出新的接口。实际上这个Stack称为适配器。有时候可能需要vector&#xff0c;但是又需…

深入解析知识付费平台的核心功能模块:满足个性化学习需求的数字化教育新星

在数字化学习的大潮中&#xff0c;知识付费平台已成为教育行业的一颗新星&#xff0c;它以满足用户需求为核心&#xff0c;提供便捷高效的学习渠道。该平台汇聚了各类专业知识&#xff0c;覆盖职业技能、生活兴趣和人文社科等多个领域&#xff0c;满足不同用户的学习需求。同时…

【二】【QT开发应用】QMake和CMake介绍,GN,QT三个窗口类的区别,QMainWindow, QWidget,QDialog

QMake和CMake介绍 qmake&#xff1a;qt独有的代码构建工具, 是一种简洁的构建工具&#xff0c;主要用于生成 Qt 项目的跨平台编译配置文件&#xff0c;语法简单&#xff0c;适合小型和中型项目。 cmake&#xff1a;C通用的代码构建工具&#xff0c;绝大部分C开源项目都使用cm…

MySQL 8.0 版本更新 要点 列表 (8.0-8.0.23)

开头还是介绍一下群&#xff0c;如果感兴趣 PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;&#xff08;共 2370 人左右…

楼顶气膜体育馆建设的关键问题解析—轻空间

随着城市化进程的加快和土地资源的日益紧张&#xff0c;楼顶气膜体育馆作为一种新兴的建筑形式备受关注。其轻盈美观、节省用地、施工便捷等特点&#xff0c;使其成为城市空间利用的理想选择。那么&#xff0c;在楼顶建设气膜体育馆有哪些关键问题需要考虑呢&#xff1f; 一、楼…

Simulink代码生成: 状态机的其他建模方法

本文研究状态机建模的一些方法和技巧。 文章目录 1 引入2 状态机建模方法2.1 状态机中的计时2.2 状态机中的计数2.3 转移顺序 3 总结 1 引入 博主一直很喜欢用Simulink中的状态机建模&#xff0c;在这里想记录一下自己平时使用Stateflow建模的心得。因为自身行业所限&#xff…

LayUI使用(二)处理表格会出现下拉框的问题

一、问题描述 如下&#xff0c;layui的表格渲染后&#xff0c;当鼠标悬停在表格项时会出现右侧的下拉框&#xff0c;layui版本较老&#xff0c;原因未知 二、处理办法 在cols里面加上width&#xff0c;也不用每个都加&#xff0c;加一部分表格项即可 注意&#xff1a;若想禁止…

全功能知识付费小程序源码系统 界面支持万能DIY装修 带完整的安装代码包以及搭建部署教程

系统概述 在当今数字化时代&#xff0c;知识付费已经成为一种重要的商业模式。为了满足市场对于便捷、高效、个性化的知识付费解决方案的需求&#xff0c;小编给大家分享一款全功能知识付费小程序源码系统。这一系统不仅具备界面支持万能 DIY 装修的独特优势&#xff0c;还配备…

推荐系统三十六式学习笔记:原理篇.矩阵分解11|facebook是怎么为十亿人互相推荐好友的?

目录 回顾矩阵分解交替最小二乘原理&#xff08;ALS&#xff09;隐式反馈推荐计算总结 上一篇中&#xff0c;我们聊到了矩阵分解&#xff0c;在这篇文章的开始&#xff0c;我再为你回顾一下矩阵分解。 回顾矩阵分解 矩阵分解要将用户物品评分矩阵分解成两个小矩阵&#xff0c…

帕金森患者在饮食上需要注意什么

帕金森病患者在饮食上应该遵循以下几个基本原则&#xff1a; 饮食清淡&#xff1a;应多吃新鲜的水果和蔬菜&#xff0c;如苹果、芹菜、菠菜等&#xff0c;以补充维生素和促进胃肠道蠕动。营养均衡&#xff1a;应多吃富含优质蛋白的食物&#xff0c;如鸡蛋、牛奶&#xff0c;以…

需要用来做3D家具展示的软件哪个网站更专业?

国内外的3D家具展示软件网站并且值得推荐的也就那么几家&#xff1a; 1、Cedreo&#xff0c;Cedreo 是一个在线3D家居设计平台&#xff0c;适合专业的房屋建筑商、改造商和室内设计师。它允许用户创建2D和3D平面图以及室内外效果图&#xff0c;拥有7000多件可定制的3D家具、材…

考HCIE费这么大劲,只管三年?

在网络工程师的职业发展道路上&#xff0c;HCIE&#xff08;华为认证互联网专家&#xff09;认证无疑是一块极具含金量的垫脚石。 但许多人对它的有效期存在疑问&#xff0c;担心费尽心思获得的认证只能维持短暂的职业优势。 重认证机制是啥样的&#xff1f;一定要重认证吗&…

0-2年的网安新人突破低薪打杂困境妙招!

2024年即将到来&#xff0c;相信有很多人依旧对网络安全行业行业非常好奇&#xff0c;也有很多对网络安全了解过的小伙伴&#xff0c;依旧想要进入网络安全行业。确实&#xff0c;网络安全行业前景大、薪资高、入门门槛也相对较低。 但是&#xff0c;对于0-2年的网安新人&…

【Java】已解决java.lang.UnsupportedOperationException异常

文章目录 问题背景可能出错的原因错误代码示例正确代码示例注意事项 已解决java.lang.UnsupportedOperationException异常 在Java编程中&#xff0c;java.lang.UnsupportedOperationException是一个运行时异常&#xff0c;通常表示尝试执行一个不支持的操作。这种异常经常发生…

晶圆切割机(晶圆划片机)为晶圆加工重要设备 我国市场国产化进程不断加快

晶圆切割机&#xff08;晶圆划片机&#xff09;为晶圆加工重要设备 我国市场国产化进程不断加快 晶圆切割机又称晶圆划片机&#xff0c;指能将晶圆切割成芯片的机器设备。晶圆切割机需具备切割精度高、切割速度快、操作便捷、稳定性好等特点&#xff0c;在半导体制造领域应用广…

【MySQL】数据类型和表的约束

1. 数据类型 分类数据类型解释数值类型BIT (M)位类型。M位数&#xff0c;默认为1范围1-64BOOL01表示真假TINYINT [UNSIGNED]8位整型SMALLINT [UNDIGNED]16位短整型INT [UNSIGNED]32位整型BIGINT [UNSIGNED]64位长整型小数类型FLOAT [ (M, D) ] [UNSIGNED]32位浮点类型&#xf…

vue echarts画多柱状图+多折线图

<!--多柱状图折线图--> <div class"echarts-box" id"multiBarPlusLine"></div>import * as echarts from echarts;mounted() {this.getMultiBarPlusLine() },getMultiBarPlusLine() {const container document.getElementById(multiBar…

MathType软件下载2024最新版_MathType官方免费下载附加详细安装步骤

MathType(数学公式编辑器)是由Design Science公司研发的一款专业的数学公式编辑工具。MathType功能非常强大&#xff0c;尤其适用于专门研究数学领域的人群使用。使用MathType让你在输入数学公式的时候能够更加的得心应手&#xff0c;各种复杂的运算符号也不在话下。 安 装 包 …

氢气传感器:呼吸疾病的隐形向导

​ ​​在医学领域&#xff0c;每一次技术革新都可能成为疾病诊断与治疗的新曙光。氢气传感器&#xff0c;这一看似不起眼的装置&#xff0c;正逐渐成为辅助诊断呼吸系统疾病的关键工具。它如同一位精准的侦探&#xff0c;穿梭于呼吸的微风中&#xff0c;捕捉着那些可能预示…