5个编写技巧,有效提高单元测试实践

1. 什么是单元测试

“在计算机编程中,单元测试又称为模块测试,是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。”
摘录来自维基百科

单元测试(Unit Testing)顾名思义就是测试一个单元,这里的单元通常指一个函数或类,区别于集成测试中的模块和系统。集成测试的测试过程通常存在跨系统模块的调用,是一种端到端的测试;而单元测试关注对象的颗粒度较小,用来保障一个类或者函数是否按照预期正确的执行。

2. 为什么要写单元测试

作为保障代码质量的有效手段之一,公司也在积极的推进单元测试。结合单测的实践,总结了以下几点单元测试的好处,认真实践过的同学,应该会有共鸣。

2.1 减少BUG,释放资源

上面这张图,旨在说明两个问题:

  • 85%的缺陷都在代码设计阶段产生;
  • 发现bug的阶段越靠后,耗费成本就越高,呈指数级别的增长。

单元测试是所有测试环节中最底层的一类测试,是第一个环节,也是最重要的一个环节。大多数缺陷是Coding阶段引入,修复的成本随着软件生命周期进展不断上升。日常研发中,在交付测试前我们对功能单元进行主流程、各种边界及异常单元测试的编写,能有效帮助我们发现代码中的缺陷。相对于后期来自测试同学或者线上异常反馈,再来进行排查定位、修复发布的成本来说,单元测试的性价比是极高的。单元测试可以有效地保障代码质量,给我们带来质量口碑的同时,也为他人和自己减少因修复低级BUG而投入的时间,能够将精力分配到其他更有意义的事情上。

2.2 为代码重构保驾护航

面对项目中历史遗留的腐化代码,我们都有推倒重来的冲动,但它毕竟经过了长时间的稳定性考验,我们又担心重构之后出现问题。这是我们经常会遇到的境况,当要重构不是非常熟悉的祖传代码,又没有充足的测试资源保障的时候,重构引入缺陷的风险还是很大的。

那如何保证重构不出错呢?Martin Fowler在《重构:改善既有代码的设计》提到:

重构是很有价值的工具,但只有重构还不行。要正确地进行重构,前提是得有一套稳固的测试集合,以帮我发现难以避免的疏漏。即便有工具可以帮我自动完成一些重构,很多重构手法依然需要通过测试集合来保障。

除了需要对业务流程有足够的了解并且熟练掌握各种设计思想、模式之外,单元测试是保证重构不出错的有效手段。当重构完成之后,如果新的代码仍然能通过单元测试,那就说明代码原有正确的逻辑未被破坏,原有的外部可见行为没有发生改变。单元测试给了我们重构的信心与底气。

2.3 既是编写单测也是CodeReview

单元测试和CR是保障代码质量行之有效的两个手段。在研发交付过程中,通常我们提交CR的时机较为滞后,评审同学指出待优化或修复的时间点也较晚,修复的风险和成本上都有所增加。

我们编写编码单元测试过程,其实也是自我CodeReview的过程。在这个过程中,我们对功能单元主流程、边界及异常进行测试,也在自我审视代码的规范、逻辑及设计。既提高了后续提交CR的质量与评审效率,也将问题提前暴露。

2.4 便于调试与验证

当项目存在多个协同方时,我们只需按照约定mock出依赖项的数据,无需等所有依赖的应用接口开发部署完成后再进行调试,提高了我们协同的效率与质量。我们将功能需求进行拆解,在开发完每一个小功能点时,即可进行单元测试的编写与验证,这种习惯能让我们对编码得到快速的验证反馈;同时,在开发完整个功能时,我们需要跑一遍项目所有的单测用例,可以清晰的感知,本次整个功能需求的改动是否对已有业务case造成影响。

如果我们能够保障每个类、函数都能通过单元测试按照预期业务逻辑执行,那整合后的功能模块或系统,出问题的概率都能大大降低。从这个意义上讲,单元测试也对集成测试、系统测试做了有力的支撑。

2.5 驱动设计与重构

设计和编码的时候,我们很难将所有的问题都想清楚。那我们知道,评判代码质量重要的的标准之一就是代码的可测性。如果对一段代码进行单测,发现难于编写,需要编写的case非常多,或者当前的测试框架无法mock依赖对象,需要依赖其他具备高级特性的测试框架时,我们需要回过头来审视代码,是否编码设计得不合理,导致代码的可测性不高。这是个正反馈的过程,让我们有针对性的进行重新设计与重构。

3. 怎样编写单元测试

3.1 单元测试框架的构建

3.1.1 单元测试框架JUnit

JUnit是目前Java语言应用最为广泛的单元测试框架,用于编写和运行可重复的自动化测试,它包含以下特性:

  • 用于测试期望结果的断言(Assertion)
  • 用于共享共同测试数据的测试工具
  • 用于方便的组织和运行测试的测试套件
  • 图形和文本的测试运行器

多数Java的开发环境都已经集成了JUnit作为单元测试的工具,开源框架对JUnit 都有相应的支持

3.1.2 单元测试Mock框架

项目中依赖关系往往往非常复杂,单元测试Mock框架做的事就是模拟被测试类的依赖项,提供预期的行为和状态,使得我们的单测可以聚焦在被测试类本身,而不必受到依赖项的复杂度的影响。

这里我们讨论常用的Mockito与PowerMock,两者都是作为单元测试模拟框架,模拟应用中复杂的依赖对象。Mockito基于动态代理的方式实现,PowerMock在Mockito基础上增加了类加载器以及字节码篡改技术,使其可以实现完成对private/static/final方法的Mock。

公司使用JaCoCo来做单元覆盖率的检测,当我们使用支持字节码篡改的mock工具的时候,可能会造成:

  • 测试失败,mock工具与jacoco同时修改字节码时引入的冲突
  • 某些类的覆盖率为0

所以我们推荐使用Mockito来作为我们的单元测试Mock框架,原因有二:

  1. 在版本3.4.0以后,Mockito支持静态方法的mock。并且作为SpringBootTest默认集成的Mock工具,所以建议大家使用高版本的Mockito,并通过它来完成静态方法的Mock
  2. 不提倡使用PowerMock,并不是一味追求单测覆盖率,而是当我们需要使用到具备高级特性mock工具时,我们需要审视代码的合理性,并尝试进行优化重构,使其具备较好的可测性

3.1.3 依赖引入

3.1.3.1 添加JUnit的maven依赖

  • Springboot项目
 
  • SpringMVC项目
 

3.1.3.2 单测Mock框架的引入

 

3.2 单测方法的命名

3.2.1 单元测试类的规范

  • 单元测试类需要放在工程的test目录下,比如xxx/src/test/java
  • 单测类的命名按照规范,应以被测类名开头,并追加Test作为结尾,比如ContentService -> ContentServiceTest

3.2.2 单元测试方法规范

3.2.2.1 测试方法的命名

好的单元测试方法名,能让我们快速知道测试的场景、意图及验证的预期。

建议采用should_{预期结果}_when_{被测方法}_given_{给定场景}

举个

 

反例

 

3.2.2.2 单测方法实现分层

单测方法的实现如果分层清晰,能让代码便于理解,一目了然,同时也能提高后续的CR的效率

这里我们建议采用given-when-then的三段落结构

举个

 

3.3 单测方法的示例

3.3.1 代码案例

 

3.3.2 单元测试代码案例

 

3.4 单测的编码技巧

3.4.1 Mock依赖对象

 
  • MockitoJUnitRunner使Mockito的注解生效或者使用初始化方法MockitoAnnotations.initMocks(this)
  • 利用@Mock模拟各种依赖对象
  • 使用@InjectMocks将mock出的依赖对象注入到目标测试对象中。以上述代码为例,单测中将docManageService注入到contentService

当然我们也可以使用直接初始化或者@Spy的方式来模拟对象,然后使用Setter方法来进行模拟对象的注入,这里介绍了较为简便的方式。

3.4.2 Mock返回值

3.4.2.1 Mock无返回值方法

 

3.4.2.2 Mock方法返回值

 

3.4.2.3 执行方法的真实调用

 

3.4.2.4 Mock方法调用异常

 

3.4.3 自动化验证

3.4.3.1 验证依赖方法的调用

 

3.4.3.2 验证返回值

 

3.4.4 其他单测技巧处理

3.4.4.1 使用Mockito模拟静态方法

 

3.4.4.2 处理Mockito注册静态方法范围

在执行mvn test时,如果有多个测试方法mock了Mockito.mockStatic(TagHandler.class),会报错,因为静态方法是类级别的,会出现注册多次的情况。可以参考下面两种解法:

1.使用@BeforeClass@AfterClass

@BeforeClass注解方法:只被执行一次;运行junit测试类时第一个被执行的方法

@AfterClass注解方法:只被执行一次;运行junit测试类时最后一个被执行的方法

示例:

 

2.在try-with-resources构造中定义模拟

 

3.4.4.3 如何mock一条链式调用

 

Mockito提供了形如tableService.select().from(params.getTableName()).where(params.getCondition()).limit(1)链式调用解决办法,mock对象的时候增加参数RETURNS_DEEP_STUBS

 

3.5 单测生成插件

IDEA有两款比较好用的单测自动生成插件TestMe与Diffblue,这里主要介绍TestMe,如果大家有比较好的插件也可以推荐。

  1. 安装:在IDEA设置中的Plguins插件里搜索TestMe,下载安装即可。
  2. 使用:在code按钮找到入口,或者直接使用快捷键option+shift+Q

3.生成的代码如下

自动生成插件方便初始化部分代码,可以提升单测编写的效率,但是也存在局限性:单测名称规范、具体实现等还是需要我们完善、补充后才能正常使用

4. 如何落地单元测试

4.1 清晰单测的价值认知

不难发现,公司内的项目还是外网开源项目,少有工程具备完善、高质量的单元测试。上文讲了为什么要写单测,这里就不再赘述了。短期来看,单测无疑会带来开发工作量和开发时长的增加,但是我们要从整个迭代周期来看单测的优势。从最终的效果来看,坚持单元测试会有效的减少迭代中的缺陷数以及缩短需求的交付周期。

4.2 将单测纳入流程规范

4.2.1 将单元测试纳入CR标准

以往我们CR只关注核心的业务代码,大多数情况下,我们在评审中可以指出代码较为明显的缺陷或者不合理的设计,但是各种条件case、边界及异常情况很难通过肉眼review出来。如果提交的CR中包含完善、高质量的单元测试,提交、评审双方的的信心都会增强。

4.2.2 发布管控

当我们提交代码后,CI可以设置运行该分支的单元测试。在发布流程中,添加单测相关的管控,比如单元测试通过率以及单元测试增量覆盖率等

4.3 单测工作量评估

对于单元测试工作量的评估,没有一个固定的标准,主要视业务逻辑复杂度而定。一般来说,如果之前没有编写过单元测试,在熟悉阶段可以根据需求的工作量对应增加20%~30%;后期熟练掌握后,增加需求工作量的10%就足够了。当业务需求涉及的case较多,单测需要覆盖这些必要流程时,我们评估工作量时,可以给自己加些时间来保障高质量的单测。

5. 后记

单元测试是一件知易行难的事情,公司也在积极宣导和建设单测文化。工作方式的改变其实难度并不大,难的是能够建立一致的共识,并从心底认可单元测试的价值,只有这样才能有效落地。

原文链接

本文为阿里云原创内容,未经允许不得转载。

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

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

相关文章

谈谈我工作中的23个设计模式

序 从基础的角度看,设计模式是研究类本身或者类与类之间的协作模式,是进行抽象归纳的一个很好的速成思路。后面阅读设计模式后,为了加深理解,对相关图片进行了描绘和微调。 从技术的角度已经有很多好的总结,本文会换…

OpenSergo 流量路由:从场景到标准化的探索

流量路由,顾名思义就是将具有某些属性特征的流量,路由到指定的目标。流量路由是流量治理中重要的一环,多个路由如同流水线一样,形成一条路由链,从所有的地址表中筛选出最终目的地址集合,再通过负载均衡策略…

传统 Web 框架部署与迁移

与其说 Serverless 架构是一个新的概念,不如说它是一种全新的思路,一种新的编程范式。 但是原生的 Serverless 开发框架却非常少。以 Web 框架为例,目前主流的 Web 框架“均不支持 Serverless 模式部署”,因此我们一方面要尝试接…

三款“非主流”日志查询分析产品初探

前言 近些年在开源领域,用于构建日志系统的软件有两类典型: Elasticsearch:基于 Lucene 构建倒排索引提供搜索功能,DocValue 存储支持了其统计分析能力。Clickhouse:列式存储是其优秀 OLAP 性能的保障。 这里把上述系…

CIPU落地专有云:是“小众需求”还是“机会之门”?

引言:2022年11月,云栖大会主论坛,阿里巴巴集团副总裁、阿里云智能基础产品事业部负责人蒋江伟分享了阿里云专有云的一项新进展 —— CIPU落地飞天企业版。在分析师峰会上,阿里巴巴集团研究员、阿里云专有云总经理刘国华也向分析师…

基于开源 PolarDB-X 打造中正智能身份认证业务数据基座

一、公司及业务介绍 中正智能是全球领先的生物识别和身份认证公司之一。我们曾负责公安部指纹算法国家标准的起草、编写,具备从算法、终端、平台、设计、生产、交付全域自研的能力,拥有多项自主知识产权的产品,并积极与高校合作开展基础研发。…

如何开发一个标准的云原生应用?

从几个数字开始说 IDC 预计到 2024 年,由于采用了微服务、容器、动态编排和 DevOps 等技术,新增的生产级云原生应用在新应用的占比将从 2020 年的 10% 增加到 60%,其中微服务的 workload 在企业内将超过 80% 。上面的四点是云原生时代所代表…

Higress实战: 30行代码写一个Wasm Go插件

前言 在11月15号的直播 《Higress 开源背后的发展历程和上手 Demo 演示》中,为大家演示了 Higress 的 Wasm 插件如何面向 Ingress 资源进行配置生效,本文对当天的 Demo 进行一个回顾,并说明背后的原理机制。 本文中 Demo 运行的前提&#x…

Serverless 的前世今生

从云计算到 Serverless 架构 大家好,我是阿里云 Serverless 产品经理刘宇,很高兴可以和大家一起探索 Serverless 架构的前世今生。 从云计算到云原生再到 Serverless 架构,技术飞速发展的轨迹都有一定规律可循,那么 Serverless 架…

eunomia-bpf 项目重磅开源!eBPF 轻量级开发框架来了

近日,在 2022 云栖大会龙蜥峰会 eBPF & Linux 稳定性专场上,来自 eBPF 技术探索 SIG Maintainer 、浙江大学的郑昱笙分享了《eunomia-bpf:eBPF 轻量级开发框架》技术演讲,以下为本次演讲内容: 大家好!…

一文看懂分布式链路监控系统

背景 传统的大型单体系统随着业务体量的增大已经很难满足市场对技术的需求,通过对将整块业务系统拆分为多个互联依赖的子系统并针对子系统进行独立优化,能够有效提升整个系统的吞吐量。在进行系统拆分之后,完整的业务事务逻辑所对应的功能会…

深度 | 新兴软件研发范式崛起,云计算全面走向 Serverless 化

11月3日,2022 杭州 云栖大会上,阿里云智能总裁张建锋表示,以云为核心的新型计算体系正在形成,软件研发范式正在发生新的变革,Serverless 是其中最重要的趋势之一,阿里云将坚定推进核心产品全面 Serverless…

适用场景全新升级!扩展 Dragonfly2 作为分布式缓存系统架构

Dragonfly2 简介 Dragonfly 作为龙蜥社区的镜像加速标准解决方案,是一款基于 P2P 的智能镜像和文件分发工具。它旨在提高大规模文件传输的效率和速率,最大限度地利用网络带宽。在应用分发、缓存分发、日志分发和镜像分发等领域被大规模使用。 现阶段 D…

sdut 最长公共子序列问题

Problem Description 从一个给定的串中删去(不一定连续地删去)0个或0个以上的字符,剩下地字符按原来顺序组成的串。例如:“ ”,“a”,“xb”,“aaa”,“bbb”,“xabb”&a…

hdu1176 免费馅饼 动态规划 二维数组实现

免费馅饼 Time Limit: 1000MS Memory Limit: 32768KBSubmit Statistic DiscussProblem Description 都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉&am…

如何通过链路追踪进行定时任务诊断

背景简介 什么是定时任务 定时任务是业务应用系统中存在定时周期性运行的业务逻辑。由于其运行于后端进程中往往存在执行状态和执行链路的不可见性《常见定时任务技术方案》。 什么是链路追踪 随着分布式微服务化架构在企业中大规模运用,业务运行的应用平台是一…

关于平台工程的开发者工具链,你还想加点啥?

前言 从 Kubernetes 诞生以来,以 DevOps、容器化、可观测、微服务、Serverless 等技术为代表的云原生,催生了应用架构新一轮的升级。有意思的是,与以往的技术迭代更新不同,原本是一个技术圈常规的一次技术实践,在千行…

阿里云联合“产学研媒”发起 BizDevOps 共促计划,助力企业提升组织效能

2012年全球最具影响力的独立研究咨询机构Forrester曾预言:“In the future, all companies will be software companies”(在未来,所有的企业都将成为软件企业) 近10年来,DevOps运动在全球和中国风起云涌,…

Kubernetes HPA 的三个误区与避坑指南

前言 云计算带来的优势之一便是弹性能力,云原生场景下Kubernetes提供了水平弹性扩容能力(HPA),让应用可以随着实时指标进行扩/缩。然而HPA的实际工作情况可能和我们直观预想的情况是不一样的,这里面存在一些认知误区。…

K8s有损发布问题探究

问题提出 流量有损是在应用发布时的常见问题,其现象通常会反馈到流量监控上,如下图所示,发布过程中服务RT突然升高,造成部分业务响应变慢,给用户的最直观体验就是卡顿;或是请求的500错误数突增&#xff0c…