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

简化Java单元测试数据

用EasyModeling简化Java单元测试

EasyModeling 是我在2021年圣诞假期期间开发的一个 Java 注解处理器,采用 Apache-2.0 开源协议。它可以帮助 Java 单元测试的编写者快速构造用于测试的数据模型实例,简化 Java 项目在单元测试中准备测试数据的工作,在提高编写效率的同时,使单元测试更加整洁易读。经过一年的维护,EasyModeling 已经在几个 Thoughtworks 内部的项目上得到了应用,并迭代发布了几个版本。

单元测试中的数据准备的困难

在企业级应用软件开发项目中编写测试代码时,针对特定的测试场景,我们需要准备相应的测试数据,以验证被测组件在给定输入下的行为。在使用 Java 语言的项目中,这些准备测试数据的代码体现为创建各种“数据模型类”的实例。这里的数据模型类,可以包括聚合模型(Aggregation Model)、数据传递模型(DTO)、值对象(VO)以及存储模型(Persist Model)等等。无论是对服务组件的测试,还是对数据模型本身的测试,我们都无可避免地需要构建这些数据模型类的实例。

在项目的起初阶段,准备数据的工作是简单的,我们只需要调用数据模型类的构造方法,传入适当的参数来创建实例即可。单元测试代码的规模不会太大,也尚且清晰易读。

但是随着产品开发工作的展开,一方面,项目中使用的这些数据模型会变得越来越复杂;另一方面,测试场景也会变得越来越多。经验上,在经过几个版本迭代的企业级应用 Java 代码中,我们通常不难找出一些拥有十几个、甚至几十个成员变量的数据模型类,并且它们之间还存在着复杂的相互持有、嵌套、继承的关系。这些数据模型类往往都是项目中的核心组件,故而也成为单元测试需要重点关注的组件。相应地,在涉及这些数据模型的单元测试中,为准备测试数据而编写的初始化数据模型类的代码量也会越来越大、越来越复杂。

这些冗杂繁复的数据初始化代码会影响单元测试本身的代码质量,造成单元测试编写成本高、易读性差、易维护性低等问题。而单元测试的质量又与生产代码的质量息息相关。例如,单元测试的编写成本过高,会使开发者越来越倾向于仅在已有测试基础上做修改,而不是为每个场景创建单独的测试,造成单个测试的职责过多;甚至使开发者放弃单元测试,降低了团队对产品质量的信心。又比如,单元测试的易读性差,导致单元测试无法承担起“测试即文档(tests as documentation)”的职责。而单元测试的易维护性低,则导致了代码很难被重构,从而单元测试不仅没有为重构提供信心,反而变成重构的桎梏。

具体来说,这些初始化数据的代码会引起三个方面的问题:

  • 对测试场景的描述不清晰
  • 构建测试数据的代码重复
  • 初始化数据模型代码的膨胀

我们可以从下面的例子中略窥端倪。你是否在你的项目中见过这样的单元测试?

这是一段典型的使用JUnit测试框架的单元测试代码。在这段单元测试代码中,被测对象是 leaveCalculator 组件的 annualLeave 方法。我们首先创建一位员工,如(a)处;然后将创建好的员工对象传入 annualLeave 方法,为其计算出应得的年假数额,如(2)处;最后断言他应该享有20天年假,如(3)处。为了简化讨论,我们暂且假设此处 annualLeave 方法的业务规则是:员工应得的年假数额只与这位员工加入公司的时间(date of joining)相关,即在代码中 (1) 处初始化的日期。

我们来详细分析这段测试代码中存在的坏味道、以及其潜在的问题。

对测试场景的描述不清晰

如前文所述,我们假设这段单元测试代码的目的是验证“入职超过5年的员工应该享有20天年假”这个业务规则。那么显然,其中只有 (1), (2), (3) 这三处是与当前测试场景相关的,它们共同构成了对上述业务规则的描述。而在 (1) 处之前传入 Employee 类构造方法的那些参数都是与当前测试场景无关的。遗憾的是,这些与测试场景无关的代码却占据了这个代码片段中的绝大部分代码行。

在实际项目中,我们会见到很多这样的单元测试,它们往往需要用几十行的代码来准备复杂的测试数据,需要初始化数个数据模型类的对象,以支持对被测组件的调用,然而这些代码中真正在描述测试场景的,却只有其中区区几行、甚至一两行。这不仅增加了测试的篇幅,还会导致阅读者无法快速聚焦在有意义的初始化条件上。就像我们在这个例子中看到的,描述测试场景的代码行(1)处混杂在大量初始化测试数据的代码行之中,造成了单元测试对测试场景的描述不聚焦。这使单元测试的阅读者很难从这段测试代码中一目了然地理解测试的意图,更遑论以测试为文档来理解业务规则。而在测试失败时,也无法快速从测试场景的数据构造出发去定位问题。

一些有经验的单元测试编写者已经注意到了这个问题,他们会在关键的测试数据初始化行末添加一些注释以示强调。然而注释本身就预示着代码坏味道,并且在重构中也是非常不安全的,甚至反而误导读者。

构建测试数据的代码重复

如果将目光从单个测试放大到单元测试组(Test Suit),我们会发现在针对同一个被测组件的不同测试场景下,初始化数据模型的代码会大量重复。例如在针对员工年假数额计算(leaveCalculator 组件的 annualLeave 方法)的测试组中,假设按照业务规则,我们需要考虑以下的测试场景:

  1. 入职不足2年的员工,应该享有10天年假;
  2. 当年入职的员工,享有按照入职时间折算的年假数额;
  3. 入职超过2年,而不足5年的员工,应该享有15天年假;
  4. 入职超过5年的员工,应该享有20天年假;
  5. 入职超过7年的员工,应该享有25天年假;
  6. 入职时间在未来(尚未入职)的员工,不应该计算年假数额(抛出异常);

不难想象,我们会分别在这6个测试场景对应的测试方法中重复地编写几乎完全相同的代码来初始化Employee类的对象。

这样的单元测试模式在企业级应用开发的场景中比比皆是。开发者经常很容易在测试第二个场景时,顺手从第一个场景的单元测试中复制初始化数据模型的代码,略作修改来描述第二个测试场景,后面的测试场景也如法炮制。这样显然会造成测试代码中存在大量的模板代码(Boilerplate code),进一步降低了代码的易读性。

通常在开发项目的实践中会引入构建者模式(Builder Pattern)或者 Object Mother 组件来消除这些模板代码。本文非常欣赏这些解决方案,下文会在此基础上做进一步讨论。

初始化数据模型代码膨胀

另外需要注意的是,前文举例的代码中为节省篇幅已经做了很多简化。我们不仅用省略号折叠了(1)处之后可能传入构造方法的更多的初始化参数,还折叠了在(b)处初始化 List<Department> departments 参数时逐个构造 Department 类对象所需要的大量细节,甚至在初始化每个Department类对象时,又另外需要构造更多的相关实例。

当然在实践中,经常使用的策略是将大量无关的属性设置成 null 或者空集合,但是这有时候会在被测组件对数据类有效性检查中被拦截。特别是在某些演进了一段时间的代码库中,我们经常会遇到的困难是,由于在测试中构造数据时采用了过多的 null 和空集合,一个新添加的数据有效性检查步骤或者切面(AOP),会造成几百个单元测试的失败。逐一修复这些失败的单元测试的工作量无疑是巨大的,同时是充满风险的,因为此时对单元测试的修改完全是为了兼容一个新添加的切面,而脱离了单元测试本身的业务上下文。

在这种情况下,开发者会越来越多选择将相似的数据有效性检查步骤散布在具体的业务代码中,而非在构造方法中统一检查、或者通过切面集中实现。可见,单元测试的不良设计,会反过来增加生产代码的维护难度,拖累了生产代码的演进。

EasyModeling提供的能力

造成开发者写出类似单元测试的原因是广泛存在的。例如,Employee 类没有提供更灵活的构造方法,也没有 Builder 模式的构造器。从 Employee 类自身的职责的角度出发,它的确没有理由提供一个仅包含 LocalDate dateOfJoining 作为参数的构造方法。在很多业务场景下,数据模型类也完全有可能就是不允许通过 Builder 模式来构造的。我们当然不能为了编写测试代码的便利,而去修改生产实现代码。又例如,代码中可能存在对 Employee 类的数据合法性校验。这些校验可能是类似切面的形式存在的,导致我们无法方便地在单元测试中忽略它。

在实际项目中,开发者很容易从“消除重复”的角度,抽象出相应的工厂类来提供测试所需要的数据模型实例。Martin Fowler 也在他的博客的短文 Object Mother 中简要讨论了相关的思路。但是在测试中使用工厂组件虽然消除了很多重复代码,却没有提供针对不同的测试场景的灵活定制能力,因此一些项目又会同时采用 Builder 模式来提供定制能力。我自己在多个项目上引入 Object Mother 来提供测试数据实例后发现,这些工厂类本身又具有非常固定的代码模板,于是我开始考虑开发一个工具来自动生成这种工厂类。

受到 Builder 模式和 Object Mother 思想的启发,我开发了 EasyModeling 来尝试简化 Java 单元测试的编写,并提高测试的可读性和易维护性。EasyModeling 是一个 Java 注解处理器库,它主要提供三个方面的功能:

  1. EasyModeling在编译期根据指定的数据模型类的结构,生成对应的数据模型工厂类,以方便单元测试快速生成数据模型类的实例。通过向 EasyModeling 注册一个数据模型类,单元测试的编写者只需要调用 EasyModeling 所提供工厂类的静态方法,就可以立即得到这个数据模型类的实例。
  2. EasyModeling 还可以在单元测试的运行时,自动初始化它所生成的数据模型实例。在生成数据模型实例时,EasyModeling 默认的行为是给数据模型实例的字段填充随机值,让开发者不需要再耗费精力去填充对测试场景无意义的属性。同时,开发者仍然有机会向 EasyModeling 指定每个数据模型类的每个字段所需的初始化方式。
  3. 另外,EasyModeling 还在其生成的工厂类中提供了一个 Builder 模式的构建器。利用这个构建器,开发者可以定制、并仅定制与当前测试场景相关的字段,使单元测试简短、清晰、易读。

在编码层面,EasyModeling 的行为完全发生在测试包中,丝毫不会侵入项目的生产实现代码。同时,EasyModeling 只会照顾开发者向它注册的数据类型类,而不会在代码库中主动搜索。所以即使是维护已久的代码库,从任何时间点引入 EasyModeling 都不会造成额外的负担。

EasyModeling简化后的单元测试

在引入了 EasyModeling 后,本文中第一节中的单元测试例子可以得到显著地简化:

除此之外,如前文提到,开发者需要在测试代码中向 EasyModeling 注册 Employee 类:

首先我们看到,在引入 EasyModeling 后,单元测试的代码在篇幅上得到了非常明显地简化。在单元测试中 (4) 处,EmployeeModeler 类就是由 EasyModeling 在编译期生成的工厂类,通过引用 EmployeeModeler 类中的静态方法 builder(),我们可以得到 Employee 类的Builder 的实例。请注意,此处使用的 Builder 类不是由 Employee 类自己编写的,也不是通过如 Lombok 这样的工具来提供的,而是由 EasyModeling 在其生成的工厂类 EmployeeModeler 来提供的。这样的好处是,为了测试而准备的 Builder 完全没有侵入生产代码。

其次,在 (4) 处生成的 Builder 类的实例中,EasyModeling 已经为我们尽可能多地填充了所有的成员变量。因此,我们接下来只需要聚焦在当前测试场景所关心的成员变量上。例如在 (5) 处,我们将 dateOfJoining 字段的内容设置为指定的日期。在可读性方面,由于避免了冗长的初始化参数,所以使开发者在阅读单元测试时,能够快速理解测试场景,进而也比较容易修改或维护单元测试。

第三,EasyModeling 在填充数据模型实例的属性时,不仅能够填充一些 Java 应用中常用的数据类型,包括基本类型、数组、集合、时间日期等等,还能够进一步填充当前数据模型所引用的其他数据模型。例如 Employee 类中引用的 List<Department> departments 列表字段。

最后,为了让 EasyModeling 帮我们生成 Employee 类的工厂类,如以上代码中 (6) 处,开发者只需要在任意的一个类上通过 @Model 注解声明即可。EasyModeling在编译期为所有被 @Model 注解声明的数据模型类生成对应的工厂(Modeler)类。

除此之外,EasyModeling 还提供了其他一些好用的特性,限于篇幅,具体的用法请参考文档。

EasyModeling的不足和未来

但是由于我的业余精力和能力都非常有限,EasyModeling 目前还处于它成长的初期,存在几点显然的不足。

第一,没有维护良好的使用文档。目前我只维护了一份项目 Readme 文件,作为简要的使用文档,导致一些略高级的使用方法和一些从新版本开始支持的功能并没有体现在文档中。

第二,没有维护文档注释。遵循代码整洁的原则,在长期从事的企业应用开发中,我几乎不会写任何形式的注释。所以我也没有意识到,在维护一个更偏底层的开源工具库时,充分的文档注释是非常必要的。一方面,文档注释便于开发者用户查看阅读,也便于有兴趣的贡献者参与开发。另一方面,由于这种较为基层的工具中无可避免地要使用一些魔法,如果没有良好的注释,随着时间推移,可能连我自己也会忘记其中的细节。

由于 EasyModeling 是一个关注单元测试的工具,而不会入侵任何生产代码,因此,在 Java 项目中引入 EasyModeling 几乎不会对项目的可靠性、安全性造成任何风险。所以如果你对这个工具感兴趣,认为它有可能帮助你提高编写测试的效率,请不妨引入到你的项目中尝试使用。

未来,由于我自己在项目上会持续使用 EasyModeling 来构建测试数据,所以我基本可以保证持续维护这个工具。在近期,我将聚焦在完善使用文档,以及修复从用户反馈的一些缺陷。在EasyModeling 的功能特性方面,虽然我手上目前依然积压着一些我自己想要实现的功能,但是我更想从用户的反馈中收集更多有趣的好主意,再来推进下一阶段的功能演进。


文/Tthoughtworks张哲
原文链接:用EasyModeling简化Java单元测试-Thoughtworks洞见

相关文章:

简化Java单元测试数据

用EasyModeling简化Java单元测试 EasyModeling 是我在2021年圣诞假期期间开发的一个 Java 注解处理器&#xff0c;采用 Apache-2.0 开源协议。它可以帮助 Java 单元测试的编写者快速构造用于测试的数据模型实例&#xff0c;简化 Java 项目在单元测试中准备测试数据的工作&…...

P1041 [NOIP2003 提高组] 传染病控制

题目 题目背景 本题是错题&#xff0c;后来被证明没有靠谱的多项式复杂度的做法。测试数据非常的水&#xff0c;各种玄学-做法都可以通过&#xff0c;不代表算法正确。因此本题题目和数据仅供参考。 近来&#xff0c;一种新的传染病肆虐全球。蓬莱国也发现了零星感染者&#…...

TypeScript -- 基础类型

文章目录 TypeScript -- 基础类型let 和 const基本类型写法布尔类型 -- boolean数字类型 -- number字符串类型 -- string数组类型元组类型枚举类型 -- enum任意类型 -- any空值 -- voidNull 和 Undefined不存在的类型 -- never对象 -- object类型断言 TypeScript – 基础类型 1…...

Cookie 与 Session 的作用及区别、结合使用

Cookie的作用 在网站中&#xff0c;http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后&#xff0c;第二次请求服务器依然不能知道当前请求是哪个用户。 Cookie的出现就是为了解决这个问题&#xff0c;第一次登录后服务器返回一些数据&#xff08;Cookie&a…...

【Redis】面试题

1. 为什么要用缓存 1. 提高系统的读写性能。 2. 减轻数据库的压力&#xff0c;防止大量的请求到达数据库&#xff0c;让数据库压力剧增&#xff0c;拖垮数据库。redis数据存储在内存中&#xff0c;高效的数据结构&#xff0c;读写数据比数据库快。 将热点数据存储在redis当中&…...

(学习笔记-硬件结构)CPU如何执行程序?

冯诺依曼模型 冯诺依曼模型主要由五部分组成&#xff1a;运算器、控制器、存储器、输入设备、输出设备。 控制器&#xff08;Control Unit&#xff09;&#xff1a;从内存中取指令、翻译指令、分析指令&#xff0c;然后根据指令的内存向有关部件发送控制命令&#xff0c;控制相…...

curl: (26) Failed to open/read local data from file/application

Windows10、Windows环境用curl命令上传文件报错&#xff1a; curl: (26) Failed to open/read local data from file/application假设我要上传的文件目录是&#xff1a; F:\我的下载\test.xlsx 错误写法1&#xff1a;使用单引号 curl -X POST "https://xxx/upload&quo…...

2023年深圳杯数学建模 D题 基于机理的致伤工具推断

致伤工具的推断一直是法医工作中的热点和难点。由于作用位置、作用方式的不同&#xff0c;相同的致伤工具在人体组织上会形成不同的损伤形态&#xff0c;不同的致伤工具也可能形成相同的损伤形态。致伤工具品种繁多、形态各异&#xff0c;但大致可分为两类&#xff1a;锐器&…...

DMA传输原理与实现详解(超详细)

DMA&#xff08;Direct Memory Access&#xff0c;直接内存访问&#xff09;是一种计算机数据传输方式&#xff0c;允许外围设备直接访问系统内存&#xff0c;而无需CPU的干预。 文章目录 Part 1: DMA的工作原理配置阶段&#xff1a;数据传输阶段&#xff1a; Part 2: DMA数据…...

【《React Hooks实战》——指导你使用hook开发性能优秀可复用性高的React组件】

使用React Hooks后&#xff0c;你很快就会发现&#xff0c;代码变得更具有组织性且更易于维护。React Hooks是旨在为用户提供跨组件的重用功能和共享功能的JavaScript函数。利用React Hooks&#xff0c; 可以将组件分成多个函数、管理状态和副作用&#xff0c;并且不必声明类即…...

Ajax详细讲解

Ajax&#xff08;Asynchronous JavaScript And XML&#xff09;即异步 JavaScript 和 XML&#xff0c;是一组用于在网页上进行异步数据交换的 Web 开发技术&#xff0c;可以在不刷新整个页面的情况下向服务器发起请求并获取数据&#xff0c;然后将数据插入到网页中的某个位置。…...

黑苹果如何在macOS Sonoma中驱动博通网卡

准备资源&#xff08;百度&#xff1a;黑果魏叔 下载&#xff09; 资源包中包含&#xff1a;AirportBrcmFixup.kext/IOSkywalkFamily.kext/IO80211FamilyLegacy.kext/OpenCore-Patcher 使用方法&#xff1a; 1.将 csr-active-config 设置为 03080000 全选代码 复制 2.在 …...

JVM-Cpu飙升排查及解决

https://blog.csdn.net/m0_37542440/article/details/123679011 1. 问题情况 在服务器上执行某个任务时&#xff0c;系统突然运行缓慢&#xff0c;top 发现cpu飙升&#xff0c;一度接近100%&#xff0c;最终导致服务假死。 2. 问题排查 1. 执行 “top” 命令&#xff1a;查看所…...

exoplayer3 ffmpeg 扩展库编译 aar,导入集成

exoplayer3 ffmpeg 扩展库编译 aar&#xff0c;导入集成。 已经编译完成的aar&#xff1a;https://download.csdn.net/download/mhhyoucom/88086822 编译项目方法&#xff1a; github下载项目&#xff1a;https://github.com/google/ExoPlayer FFmpeg 模块提供 &#xff0c;…...

Shell免交互

免交互 免交互就是&#xff1a;不需要人为控制就可以完成的自动化操作&#xff0c;自动化运维 Shell脚本和免交互是一个概念&#xff0c;是有两种写法。 Here Document 免交互 使用I/O(输入/输出)重定向的方式将命令的列表提供给交互式的程序或者命令cat read 是一种标准输入…...

设计模式之四:工厂模式

引言&#xff1a;除了使用new操作符之外&#xff0c;还有更多制造对象的方法。同时&#xff0c;实例化这个活动不应该总是公开地进行。 1.简单工厂模式 这里有一些相关的具体类&#xff0c;要在运行时有一些具体条件来决定究竟实例化哪个类。这样的代码&#xff08;if..elseif…...

斩获CVPR 2023竞赛2项冠军|美团街景理解中视觉分割技术的探索与应用

总第569篇 2023年 第021篇 视觉分割技术在街景理解中具有重要地位&#xff0c;同时也面临诸多挑战。美团街景理解团队经过长期探索&#xff0c;构建了一套兼顾精度与效率的分割技术体系&#xff0c;在应用中取得了显著效果。同时&#xff0c;相关技术斩获了CVPR 2023竞赛2项冠军…...

UE4/5C++多线程插件制作(十五、将模板统一,修改统一后的其他类,修改继承,修改返回类型等)

目录 MTPManageBase.h MTPAbandonable.h MTPAbandonableManage.h MTPThreadInterface.h MTPThreadAgendyManage.h MTPThreadTaskManage.h MTPManage.cpp MTPThreadTaskManage.h...

K8S系统监控:使用Metrics Server和Prometheus

Kubernetes 也提供了类似的linux top的命令&#xff0c;就是 kubectl top&#xff0c;不过默认情况下这个命令不会生效&#xff0c;必须要安装一个插件 Metrics Server 才可以。 Metrics Server 是一个专门用来收集 Kubernetes 核心资源指标&#xff08;metrics&#xff09;的…...

数据结构基础之排序算法

在数据结构中&#xff0c;常见的排序算法有以下几种&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff1a;通过比较相邻元素并交换它们的位置&#xff0c;每轮将最大&#xff08;或最小&#xff09;的元素冒泡到末尾&#xff0c;重复执行直到排序完成。 fun…...

Spark(37):Streaming DataFrame 和 Streaming DataSet 创建

目录 0. 相关文章链接 1. 概述 2. socket source 3. file source 3.1. 读取普通文件夹内的文件 3.2. 读取自动分区的文件夹内的文件 4. kafka source 4.1. 导入依赖 4.2. 以 Streaming 模式创建 Kafka 工作流 4.3. 通过 Batch 模式创建 Kafka 工作流 5. Rate Source…...

SpringBoot集成Thymeleaf

Spring Boot 集成 Thymeleaf 模板引擎 1、Thymeleaf 介绍 Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。 Thymeleaf 的主要目标是为开发工作流程带来优雅的自然模板&#xff0c;既可以在浏览器中正确显示的 HTML&#xff0c;也可以用作静态原型&#xf…...

算法练习(2):牛客在线编程03 二叉树

package jz.bm;import jz.TreeNode;import java.util.*;public class bm3 {/*** BM23 二叉树的前序遍历*/public int[] preorderTraversal (TreeNode root) {ArrayList<Integer> list new ArrayList<>();preOrder(root, list);int[] res new int[list.size()];fo…...

回归预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现TCN-BiLSTM时间卷积…...

Linux 系列 常见 快捷键总结

强制停止 Ctrl C 退出程序、退出登录 Ctrl D 等价 exit 查看历史命令 history !命令前缀&#xff0c;自动匹配上一个命令 &#xff08;历史命令中&#xff1a;从最新——》最老 搜索&#xff09; ctrl r 输入内去历史命令中检索 # 回车键可以直接执行 ctrl a 跳到命令开头 …...

OA系统构建排座

目录 一.排座的介绍&#xff0c;作用 1.排座介绍 A.前端实现 B.数据库实现 C.后端实现 2.排座作用 A.座位预订 B.座位安排 C. 实时座位状态显示 二.利用Layui实现排座 1.基础版(通过htmlcssjs实现) A.基础版源码&#xff08;html&#xff09;&#xff1a; 2.进阶版 …...

微信小程序 居中、居右、居底和横向、纵向布局,文字在图片中间,网格布局

微信小程序居中、居右、横纵布局 1、水平垂直居中&#xff08;相对父类控件&#xff09;方式一&#xff1a;水平垂直居中 父类控件&#xff1a; display: flex;align-items: center;//子控件垂直居中justify-content: center;//子控件水平居中width: 100%;height: 400px //注意…...

【C++】总结2

文章目录 1.final和override关键字2.extern "C"的用法3.野指针和垂悬指针(悬空指针)4.指针指向的内存被释放是什么意思5.C和C的类型安全6.C中的重载、重写&#xff08;覆盖&#xff09;和隐藏的区别 1.final和override关键字 final和override是C11引入的关键字&…...

vue2项目中使用svg图标

在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后&#xff0c;页面上加载的不再是图片资源, 这对页面性能来说是个很大的提升&#xff0c;而且我们SVG文件比img要小的很多&#xff0c;放在项目中几乎不占用资源。 1、安装SVG依赖插件并配置加载器和路径 npm instal…...

阿里云盘自动每日签到无需部署无需服务器(仅限学习交流使用)

一、前言 阿里云盘自动每日签到&#xff0c;无需部署&#xff0c;无需服务器 执行思路&#xff1a;使用金山文档的每日定时任务&#xff0c;执行阿里云盘签到接口。 二、效果展示&#xff1a; 三、步骤&#xff1a; 1、进入金山文档网页版 金山文档官网&#xff1a;https:…...