软件测试技术之单元测试—工程师 Style 的测试方法(3)

如何设计单元测试?

单元测试设计方法

单元测试用例,和普通测试用例的设计,没有太多不同,常见的就是等价类划分、边界值分析等。而测试用例的设计其实也是开发者应该掌握的基本技能。

等价类划分

把所有输入划分为若干分类,从每个分类中选取少数有代表性的数据做为测试用例。

例如,一个方法计算输入参数的绝对值的倒数,如果是输入是 0,则抛异常。那么对这个方法写测试的话,就应该有三个等价类,输入是负数、0 以及正数。所以我可以选取一个负数、一个正数以及 0 来设计三个测试用例。

再举个例子,某个方法是根据医生的认证状态,发送不同的消息。那么等价类可能有三种,未认证、普通认证但无权威认证、普通认证且权威认证,某些情况下可能还会包括无普通认证但有威认证。

边界值分析

边界值是指划分等价类后,在边界附近的一些输入数据,这些输入往往是最容易出错的。

例如,对于上面计算绝对值的倒数的例子,那么边界值就包括 Integer.min、-1、0、1、Integer.max 等。再举个例子,文本框输入范围为 1 - 255 个字符,那么等价类按输入长度划分有三类 0、1 到 255、大于 255,而边界值则包括 0、1、2、254、255、256 等。

其他类似于空数组、数组的第一个和最后一个、报表的第一行和最后一行等等,也是属于边界值,需要特别关注。

其他方法

除了上面提到的几种,测试设计方法还有几种常用的:

场景法。场景法是根据模块实际使用的场景,例如 API 的实际调用方法、系统的实际需求场景和处理逻辑创建的用例。这种方法比较直观,并且用例贴近实际需求的,不可忽视。

错误推测。错误推测其实就是凭直觉,考虑最容易出错的情况来设计用例。例如,我们直到新用户、重复请求、并发、弱网、大数据量等情况都是非常容易出错的,那么可以针对性的设计用例。错误推测需要测试设计者比较熟悉业务逻辑,并且经验丰富。

其他还有因果图、正交法等方法,这里就不说了。

覆盖率

如果按照前面的用例设计方法,可能会设计出很多用例。我们不可能也没有必要把每一个用例都写成单元测试。

怎么确认用例是否足够呢?一个很重要的参考指标就是代码覆盖率。

覆盖率指标

常用的覆盖率指标有四种:

语句覆盖:每条语句至少执行一次。

分支覆盖:每个分支至少有一次为真、一次为假。

条件覆盖:每个分支的每个条件至少有一次为真、一次为假。

路径覆盖:对所有的分支、循环等可能的路径,至少都要覆盖一次。

我们以这个简单的代码为例,看看这四种覆盖率到底是什么意思。

if (a && b) {

// X

}

// Y

if (c || d) {

// X

}

语句覆盖。只需要一个测试用例,让 a && b 和 c || d 都为真,系统会依次执行 X、Y、Z 三个的代码段,就能做到语句覆盖。

分支覆盖。至少需要两个测试用例,让 a && b 和 c || d 都各为真假,例如用例1 a && b 为真和 c || d 为假,用例2 则反过来,既可让两个条件分支都各为真一次,为假一次。

条件覆盖。至少需要四个测试用例,条件 a 和 b 的四种组合都要执行一次,条件 c 和 d 的四种组合也都要执行一次。

路径覆盖。至少需要八个测试用例,条件 a、b、c 和 d 的所有组合都要执行一次。

可以看到,要做到条件覆盖甚至路径覆盖,会需要非常多的测试用例。一般情况,对于复杂的逻辑,单元测试做到分支覆盖就不错了,必要的话再做更多完全的覆盖。

Jacoco 覆盖

Jacoco 的覆盖率略有不同,这里简单说一下。

指令覆盖(Instructions),覆盖所有的 Java 代码指令。

分支覆盖(Branches),和上面的分支覆盖基本是一样的。

圈复杂度覆盖(Cyclomatic Complexity),可以认为就是路径覆盖率。

语句覆盖(Lines),和上面的语句覆盖基本是一样的。

方法覆盖(Methods),覆盖所有的方法。

类覆盖(Classes),覆盖所有的类。

怎么写有效的单元测试?

到现在,相信大家对怎么写单元测试应该有一定概念了。但是很多人也会有疑问:

单元测试耗费太多时间,会不会降低生产效率?

单元测试会不会很难维护?比如修改代码时还总是需要修改单元测试。

关于第一个问题,相信大家应该都能理解,如果我们在开发时发现 BUG,那么解决它是很容易的;但是一旦到了集成、验收甚至上线之后,那么要解决它就要花费比较大的代价了。业界很早就有共识,并且有不少数据可以证明,有效的单元测试虽然要花费更多编码时间,但是可以很大的减少项目的集成、测试和维护成本。

注意上面提到很重要一点是,单元测试必须是有效的,如果我们发现单元测试很难维护,那往往是因为我们没有写出有效的单元测试。

不是所有的代码都需要单元测试

写单元测试我们也需要考虑投入产出比,例如下面这些情况,写单元测试的投入产出比可能会较差。

短期的或者一次性的项目,例如 Demo、数据更新脚本。

业务简单的,不含太多逻辑的模块。例如获取或者查找一个数据,或者没有分支条件的业务逻辑等。

UI 层,相对而言比较难做单元测试,除非 UI 本身就有比较复杂的逻辑(其实某些 UI 框架也提供了单元测试工具)。

那么那些情况下要写单元测试呢?简单来说,就是两类。

逻辑复杂、不容易理解、容易出错的模块。例如,计算闰年的方法、订单下单等。

公共模块或者核心的业务模块。

即使对于需要写单元测试的模块,我们也应该关注最核心最重要的测试用例,而没必要单纯的追求覆盖率,或者追求条件覆盖甚至路径覆盖,一般做到分支覆盖就可以了。另外一个有效的方法是,对于出现的每一个 BUG,添加一个单元测试。

单元测试应该是稳定的

这里稳定的第一个含义是,单元测试不应该经常需要修改。如果单元测试经常因为底层实现逻辑的变动而需要修改,那一定不是好的单元测试。也就是说,被测单元的接口应该是稳定的、设计良好的、易于扩展的。

稳定的第二个含义是,单元测试的结果应该是稳定的。如果在不同的环境、不同的情况运行单元测试,会返回不同的结果,那就不是好的单元测试。如果测试需要依赖特定的数据、文件等,那需要有前置的初始化脚本确保依赖的数据、文件在所有环境都存在并且是一致的。

单元测试应该是灰盒测试

单元测试应该覆盖核心逻辑的各种分支、边界及异常,但是避免涉及易变的实现逻辑。也就是说,我们不应该把单元测试当成完全的白盒测试,但也不是黑盒测试,而应该把它当成介于白盒和黑盒之间的灰盒测试。

被测代码应该是抽象良好的

如果我们发现一段代码很难编写单元测试,常常是因为这段代码没有符合良好的抽象规范,比如没有使用 DI、不符合单一职责原则、或者依赖了全局的公共变量和方法等等。我们可以考虑优化这段代码,再来尝试单元单元测试。

谈谈到底什么是抽象,以及软件设计的抽象原则 介绍了软件抽象的原则,这里就不再重复了。

编码时就应该同时写好单元测试

这样我们才能在调试时就发挥单元测试的优势,对代码的任何修改都能得到即时反馈。如果是后面再补充单元测试,一方面对实现可能已经不太熟悉了,编写测试的代价更大了;另一方面,单元测试能发挥的作用也变小了。不过即使这样,对那些需要长远维护的项目,编写单元测试也还是很有用的。

单元测试的代码质量也很重要

单元测试也是代码,也是需要不断维护的。所以我们不应该随随便便的去写单元测试,而是要把他们也当成普通代码一样,要做到高质量、模块化、可维护。

为什么要写单元测试之终极原因

终极原因是,作为一名优秀的工程师,如果被 QA 和产品经理 Challenge 有 BUG,能忍吗?而我们工程师当然要用工程师 Style 的测试方法,那就是自动化的单元测试了,不是吗?

文章来源:网络 版权归原作者所有

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系小编,我们将立即处理

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

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

相关文章

[UE4][C++]使用qrencode动态生成二维码

一、使用CMake编译x64版本qrencode 下载地址 GitHub - fukuchi/libqrencode: A fast and compact QR Code encoding libraryA fast and compact QR Code encoding library. Contribute to fukuchi/libqrencode development by creating an account on GitHub.https://github.…

2023/08/13_______JVM(CG)垃圾回收 算法(复制算法,标记清除,标记清除压缩)

JVM GC算法 复制算法 1,每一次GC都会将伊甸(Eden)活的对象移到幸存区中:一旦Eden区被GC后 就会是空 只要有内容就是from区 谁空谁是to区 内存会从 伊甸->幸存区to->幸存from(这个时候to和from交换区域&#xf…

EXPLAIN使用分析

系列文章目录 文章目录 系列文章目录一、type说明二、MySQL中使用Show Profile1.查看当前profiling配置2.在会话级别修改profiling配置3.查看profile记录4.要深入查看某条查询执行时间的分布 一、type说明 我们只需要注意一个最重要的type 的信息很明显的提现是否用到索引&…

kafka线上问题优化

如何防止消息丢失 生产者: 使用同步发送把ack设成1或者all(非0,0可能会出现消息丢失的情况),并且设置同步的分区数>2 消费者:把自动提交改成手动提交 如何防止重复消费 在防止消息丢失的方案中&#…

leetcode 力扣刷题 数组交集(数组、set、map都可实现哈希表)

数组交集 349. 两个数组的交集排序+双指针数组实现哈希表unordered_setunordered_map 350. 两个数组的交集Ⅱ排序 双指针数组实现哈希表unordered_map 349. 两个数组的交集 题目链接:349. 两个数组的交集 题目内容如下,理解题意&#xff1a…

梯度爆炸和梯度消失的原因以及解决方法

文章目录 1、原因:2、解决方法 1、原因: 梯度消失和梯度爆炸的根本原因是因为在反向传播过程中,使用链式法则计算时,累积相乘效应导致梯度过大或者过小主要原因有: 1)激活函数:例如sigmoid或者…

聊聊火车的发展

目录 1.火车的概念 2.火车的发展历史 3.火车对战争的影响 4.火车对人们出行造成的影响 1.火车的概念 火车是一种由机械动力驱动的陆上交通工具,通常用来运输人员和货物。它由一列或多列的连接在一起的车厢组成,有轨道作为其行驶的基础,并通…

重建与突破,探讨全链游戏的现在与未来

全链游戏(On-Chain Game)是指将游戏内资产通过虚拟货币或 NFT 形式记录上链的游戏类型。除此以外,游戏的状态存储、计算与执行等皆被部署在链上,目的是为用户打造沉浸式、全方位的游戏体验,超越传统游戏玩家被动控制的…

mysql面试

基础篇 通用语法及分类 DDL: 数据定义语言,用来定义数据库对象(数据库、表、字段)DML: 数据操作语言,用来对数据库表中的数据进行增删改DQL: 数据查询语言,用来查询数据库中表的记录DCL: 数据控制语言,用…

php正则替换文章的图片

要使用正则表达式替换文章中的图片链接,可以按照以下步骤进行操作: 1. 获取文章内容:首先,你需要获取包含图片链接的文章内容。你可以从文件中读取文章,或者从数据库中检索文章内容。 2. 使用正则表达式匹配图片链接…

JAVA编程学习笔记

常用代码、特定函数、复杂概念、特定功能……在学习编程的过程中你会记录下哪些内容?快来分享你的笔记,一起切磋进步吧! 一、常用代码 在java编程中常用需要储备的就是工具类。包括封装的时间工具类。http工具类,加解密工具类&am…

day17 | 110.平衡二叉树、257. 二叉树的所有路径、404.左叶子之和

目录: 解题及思路学习 110.平衡二叉树 https://leetcode.cn/problems/balanced-binary-tree/ 给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差…

Linux学习之firewallD

systemctl status firewalld.service查看一下firewalld服务的状态,发现状态是inactive (dead)。 systemctl start firewalld.service启动firewalld,systemctl status firewalld.service查看一下firewalld服务的状态,发现状态是active (runni…

okcc呼叫系统导入呼叫名单/客户资料的数量上限,okcc通话声音小有哪几种处理办法?

系统导入呼叫名单/客户资料的数量上限 呼叫名单一次最多十万 客户资料一次最多五万 通话声音小有哪几种处理办法? 1、IP话机:通过话机上的音量调节按钮来进行调节。 2、模拟话机:修改语音网关上的增益来实现。 “ 往IP增益”表示电话呼入…

stable diffusion 运行时报错: returned non-zero exit status 1.

运行sh run.sh安装stable diffusion时报错:ImportError: cannot import name builder from google.protobuf.internal (stable-diffusion-webui/venv/lib/python3.8/site-packages/google/protobuf/internal/__init__.py) 原因:python版本过低&#xff0…

ubuntu16.04制作本地apt源离线安装

一、首先在有外网的服务器安装需要安装的软件,打包deb软件。 cd /var/cache/apt zip -r archives.zip archives sz archives.zip 二、在无外网服务器上传deb包,并配置apt源。 1、上传deb包安装lrzsz、unzip 用ftp软件连接无外网服务器协议选择sftp…

股票交易c接口包含哪些调用函数?

股票交易的C接口中可能包含多个调用函数,具体的调用函数取决于所使用的接口规范和交易所的要求。接下来看看下面是一些可能常见的股票交易C接口调用函数的示例: 1. 连接函数(Connect):用于与交易所建立网络连接。 2.…

CSS(JavaEE初阶系列14)

目录 前言: 1.CSS是什么 1.1CSS基本语法 2.引入样式 2.1内部样式表 2.2行内样式表 2.3外部样式 3.选择器 3.1选择器的种类 3.1.1基础选择器 3.1.2复合选择器 4.常用元素属性 4.1字体属性 4.2文本属性 4.3背景属性 4.4圆角矩形 4.5元素的显示模式 4…

​LeetCode解法汇总2682. 找出转圈游戏输家

目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 描述: n 个朋友…

【Leetcode】84.柱状图中最大的矩形(Hard)

一、题目 1、题目描述 给定 n n n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 示例1: 输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10示例2:…