聊聊变异测试

软件质量保障

所寫即所思|一个阿里质量人对测试的所感所悟。

1. 介绍

有句话说:证实容易,证伪难。正如测试一样,证明缺陷存在容易,但证明不存在缺陷难。而变异测试颠覆了这一原则,如果我们知道存在缺陷,那么我们的测试结果会如何反映测试的质量呢?

随着工程师越来越多地采用更自动化的软件验证方法,以及在不断缩短的发布周期中对更高品质的软件输出的需求日益增长,变异测试帮助我们退一步评估,我们是否真的应该对我们的测试充满如此信心。

Mutation testing is the process of heuristically determining semantics of your program that are not covered by tests.

变异测试是通过启发式方法确定程序中未被测试覆盖的语义的过程。——Markus Schirp

在多数软件测试方法中,很难预判能否在测试过程中发现缺陷,往往直到这些缺陷在后续的测试环节被发现,甚至是更糟的情况下,在生产环境中出现时才会被注意到。这对于每位测试经理来说都颇为熟悉,因为他们需要根据生产环境发布后获得的经验来反馈并改进测试流程。无疑,是否存在一种更佳的方法来揭示测试覆盖中的问题呢?

变异测试作为一种测试技术,其历史可追溯至1971年,近年来越来越受到重视,现已发展出十几种相关测试工具,并广泛应用于各种软件环境。可用工具的数量自1981年的不足5种显著增长,到2013年已超过40种。

变异测试处在依赖系统期望行为正式规范的测试技术灰色地带,与之并列的还有模糊测试、变形测试以及探索性测试。从根本上讲,变异测试是一种实践,即对软件(所谓的“变异体”)的多个版本执行一组测试。每个待测软件版本都有意且通过编程方式注入了不同的缺陷。每一次测试迭代都是基于启发式方法在软件的一个微小差异版本上进行的——这些规则对应于常见缺陷——注入不同的缺陷。这些版本被称为“变异体”,意指它们是小规模的变异。测试的目的通常是确定哪些缺陷能被测试程序发现,从而导致执行失败。

之所以将软件的这些人造缺陷版本称为“变异体”,是因为它们的变异方式类似于人类基因在自然进化过程中的变异,这与人工智能中使用遗传算法解决搜索和优化问题有相似之处。

变异测试的好处可能非常明显,它能为以下方面提供极为有用的洞察:

• 自动化测试的质量和覆盖范围,尤其是断言和验证的覆盖充分度情况

• 特定测试设计技术的成功实施

• 代码不同模块的复杂度和可维护性

• 通过测试追溯组件到整体业务功能的能力

它可以作为一种方法来改进现有测试集,以确保有足够的测试验证或优先针对代码的特定模块进行测试。最终,它提供了一组关于质量的真实信息,这些信息通过其他方式是无法获得的。

2. 自动化测试

从概念上讲,变异测试并不局限于自动化测试;从原则上讲,它是一种可以应用于手工测试的方法。然而,在大多数情况下与运行数千次手工测试相关的成本无法与收益相提并论。

自动化测试的核心挑战之一是确保断言和验证点的同步。测试工程师理应感知更多的上下文知识,并利用专家经验来确定测试用例的预期并进行断言。与手工测试不同,自动化测试集的本质是,它们只会验证预期存在的缺陷——换句话说,它只会对预期内的返回做断言。举个最极端的例子,你可以创建一个自动化测试,启动要测试的软件,只需验证它可以成功运行,而不对程序输出内容做任何断言。这将是一个非常易于维护的测试,但也是一个价值非常低的测试,因为它除了告诉我们程序正常运行外,没有告诉我们其他内容。如果使用变异测试来评估这种假设软件和测试,得分将非常低,因为软件的变异版本只有在注入的缺陷导致软件完全运行失败时才会使测试报错。变异测试不能解决测试可维护性的问题,但它确实能对单个测试的实际价值提供一定的见解。这一点也很重要,因为即使在一个自动化程度很高的环境中,人们也往往不会执行全面的组合测试,因为它被认为是一种不切实际的资源使用方式。任何测试设计技术都存在一个风险,即测试用例爆炸,也就是说,当你专注于一种特定的测试设计技术时,测试的数量会急剧增加。

3. 代码覆盖率和变异运算符

变异测试是一种黑盒测试技术,测试人员做测试设计和执行不需要了解代码的内部执行逻辑,但是变异植入人员必须非常熟悉了解代码。事实上,生成的变异体与底层代码的语言和结构密不可分,真正的缺陷也是如此。当变异植入人员将缺陷注入代码,这里变异体通常可以由编写代码的开发工程师自定义,包括:

•从代码中删除语句

•从代码中插入语句

•更改代码中的条件

•替换变量

让我们回顾一些基本的编程概念以及它们与缺陷、代码覆盖率和变异测试的联系。在大多数编程语言中,可执行语句表示要执行的操作,例如将一个值(例如true或false)分配给一个变量。ISTQB术语表将此定义为:“当编译时,该语句被编译成目标代码,并且在程序运行时将按顺序执行,并且可能对数据执行操作。”例如,在下面的代码中,所有不以IF/ELSE开头的代码都是语句:

allowEntrance = false if(customerHasMembershipCard or customerHasAccessCard):  allowEntrance = true   price = 0 else:   if(weekend):     allowEntrance = true     price = 10

可执行测试集覆盖语句的程度通常被称为语句覆盖率,语句覆盖率是测试集执行的语句的百分比。

语句覆盖率=执行语句数/总语句数

要达到上述代码的完整语句覆盖,你需要两个测试,因为要执行每个语句,需要通过代码中的两个互斥路径。另一种覆盖方法是覆盖每个分支或决策,基本上,只要包含条件语句(如if、for、while),就要确保条件语句的两个结果都被评估。要达到上述示例的完整分支覆盖,你需要一个额外的测试,以覆盖weekend变量是否为真或假。

最后,条件覆盖率是一种代码覆盖率度量标准,它衡量的是每个单个条件是否已被评估为真或假。这可以计算为:

因此,再次以上面的例子为例,确保测试覆盖了会员身份和访问卡场景,为我们不断增多的测试集添加另一个测试。仅使用代码覆盖率指标来衡量自动化测试质量的问题在于,这些指标没有一个可以评估我的测试是否实际上检查了客户是否被允许访问,或者软件计算了多少费用。这些状态和变量的验证不包括在指标中。在自动化测试中测量代码覆盖率固然很好,但这只是画面的一部分。代码覆盖率只告诉你已经执行的逻辑和分支,它不能真正衡量你的测试是否获得了大量的功能覆盖数据,也不能告诉你你的测试是否有效地检测到了缺陷。验证系统响应(有效比较实际结果与预期结果)是实现自动化测试的关键部分,直接检查一个变量是否合理是很容易的;然而,随着接口变得越来越复杂,围绕验证的设计主观性也在增加。变异操作引擎将变异操作符应用于代码,它们支持的操作符各不相同,实际上用户通常可以配置如何应用这些操作符。例如,著名的Mothra研究和支持工具使用了表1中所示的Fortran中的操作符。这些操作符有些过时,因为操作符随着面向对象技术的发展而发展。

当然,潜在操作符的数量及其导致的软件代码变异是巨大的,任何实现都不可能是穷尽的。通过一个变异操作符运行测试软件可能会导致代码中的许多变化。例如,基于规则的操作符应用于此代码:

allowEntrance = false if (customerHasMembershipCard or customerHasAccessCard):   allowEntrance = true   price = 0 else:   if(weekend):     allowEntrance = true     price = 10

在这个例子中,红色项目将被更改。第一个将被初始化为true,第二个将把or改为and,第三个和第四个将通过添加not来否定布尔值。因此,将被编译出四个测试系统的版本,并且变异测试例程应该针对每个版本运行所有自动测试。为了成功检测每个变异体,测试集需要具有以下特征:

•测试如果客户没有两张卡,并且不是weekend,则不允许客户进入。这将测试变异1中未初始化变量的情况。

•测试如果客户有一张卡,但没有另一张卡(完整条件覆盖也需要测试这种情况),然后验证客户被允许进入。

•测试如果客户在weekend没有卡是否被允许进入。

•替换变量​

正如你所看到的,尽管为达到代码覆盖率而构建的测试集会通过代码执行类似的路径,但变异测试指标允许对测试应执行的验证进行更具体的描述。毕竟大多软件缺陷是在编码过程中引入的。例如,常见的“下标偏移”缺陷,即程序员指示循环迭代一次或多次,或者太少——或者误算了边界条件——可以直接通过边界值分析和等价划分等测试设计技术来解决。同样,这种缺陷通常是由变异测试操作员注入的。变异测试过程可以概括如下:

1. 通过插入缺陷来创建变异体。

2. 变异体创建后,选择并执行测试。

3. 如果变异体执行测试时测试失败,则变异体将被“杀死”。

4. 如果变异体测试的结果与基础软件相同,则变异体“存活”。

5. 可以添加新的测试,修改现有的测试或重构代码,以增加“杀死”的变异体数量。一些变异体无法被检测到,因为它们会产生与测试的原始软件等价的输出,这些被称为“等价变异体”。一旦整个过程执行完毕,就可以计算变异体得分。这是杀死的变异体与变异体总数的比率。这个分数越接近1,测试集和软件的质量就越高。

变异得分=杀死的变异体数量/总变异体数量

4. 变异测试的挑战和策略

进行变异测试所需的成本比较高,而且需要大量花费时间来检查和修复发现的问题,需要考虑成本如下:

•生成变异体的编译时间成本

•在变异体上运行测试的运行时间成本

•分析结果的人力成本。

编译时间正如前面提到的,变异测试的一个主要问题是执行成本。变异体的数量是代码行数和数据对象数之积,但通常情况下,生成的变异体的数量通常是代码行数的平方。一些策略已经尝试减少执行量:

  • 抽样——在软件的逻辑区域和相关测试中仅执行变异体的随机样本。

  • 聚类——使用无监督机器学习算法(例如,K-means)来选择变异体。

  • 选择性测试——减少变异体操作符的数量,即用于注入缺陷的启发式方法,可以减少变异体的数量60%。

  • 高阶变异——一阶变异体是那些只注入一个缺陷的变异体;二阶变异体是在多次变异迭代中注入多个缺陷的变异体。高阶变异体更难杀死,而仅关注二阶变异体已被证明可以减少工作量,而不会减少覆盖率。

  • 增量变异——只对变更代码进行变异测试,而不是测试中的整个代码库。

我们知道,缺陷具有集聚性,也许一个简单的策略,将技术应用于有限、复杂、高风险和充满缺陷的功能区域,可以提供成本与收益的适当平衡。相反,代码中有些语句我们不必担心。例如,从变异中排除所有日志语句可能是适当的,并导致更少的变异体进行测试。另一种减少所需时间和资源的方法是直接与编译器集成。早期的变异测试方法单独编译每个变异体;然而,更现代的方法编译一次,然后对中间形式(如字节码)进行变异。这在编译性能方面具有显着优势,但在评估每个变异体的执行时间方面没有优势。

运行时间运行变异测试周期的运行成本可能是巨大的。当然,在大多数情况下,执行可以水平扩展,因为测试可以在多台机器上运行,或者可以通过使用更强大的机器进行垂直扩展。为了应用水平可扩展性,有必要在选择适当的变异测试工具时考虑这一点,并支持此类方法。还需要考虑提高测试运行时间的经典测试自动化技术。当测试运行一次时,内置到测试中的硬编码等待可能不会引起注意,但当扩展到数百或数千次执行时,就会成为一项重要的成本。在尝试引入变异测试之前,应优化自动化测试以提高性能。

分析两个相关的挑战是Oracle问题以及减少等价变异的问题。Oracle问题远非变异测试所独有,它适用于任何难以确定测试是否预期的测试领域。当无法杀死变异体时,就会发生这种情况,因为需要在测试中实现的断言太难实现。当软件的确定性较低,并且难以理解检测不到变异是否实际上是有意义时,也会发生这种情况。那么最大的挑战是什么:

最大的问题是测试oracle问题。对于小型项目来说,这不是什么大问题。然而,对于大型项目来说,变异会产生数千个变异体,其中数百个是有效的。目前还不清楚开发工程师应该如何处理它们——人工review这些显然是不切实际的。

减少等价变异的问题也很重要,也就是说,变异不会导致输出可观察变化。这可能是因为未执行过的僵尸代码;变异只改变软件的速度;或者变异只改变内部使用的数据,不影响最终输出。

让我们看一个等价的变异:

def foo(i): return i + 1 + 0

一个潜在的等价变异的例子是删除“+ 0”。虽然这会改变正在测试的软件,但它不会改变输出,因为向一个数字加零没有任何实际效果。这正是关键所在,代码无关紧要,应该删除。

5. 变异测试工具

变异测试工具的种类繁多,这在一定程度上是因为它们与实现中使用的编程语言有着内在的联系。你通常不能使用为 C++ 设计的工具来编写 Java。选择一个支持底层技术栈、支持让你配置所需的变异启发式方法、与你的开发环境集成并支持并行和分布式执行的工具至关重要。使用这些工具可以简单到在你的构建配置中注入依赖项。选择和评估适合你的工具实际上比传统工具选择更重要。一个原因是这些工具将带有不同的内置默认操作符和策略。正如前面提到的,这些操作符和策略有效地决定了技术方法,并直接影响结果。该工具还可以确定如何扩展运行时执行,而选择一个支持有限的工具可能会导致不切实际的时间表。

然而,你需要考虑的不仅仅是变异工具:

作为一个实施者,我看到的更大的问题应该是如何无缝集成。如何将变异集成到现有的基础设施中:各种构建系统、测试框架、CI 管道、IDE 等

对于测试周期而言,还有很多工具。例如,Pitest,一个Java变异引擎;Cucumber插件允许你与Cucumber行为驱动开发集成。同样,SonarQube,一个流行的代码质量监控集,有插件允许你显示变异测试运行的详细结果。一些变异测试引擎还提供IDE插件,加速反馈循环,并允许你在代码上下文中查看结果。

6. 总结一下

本文主要是希望开阔大家的视野,以另一种视角来看测试覆盖率。虽然变异测试仅频繁应用在单元测试层面,但这些概念可以应用于整个软件质量保证实践。它不仅是一种评估自动化测试价值的有效方法,而且是一种了解代码复杂性的方法,以及定量了解代码质量和测试覆盖率的方法。这些概念也可以应用于代码、输入数据、环境和其他技术。

- END -


下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!

关注「软件质量保障」微信公众号

好文推荐

往期推荐

聊聊工作中的自我管理和向上管理

经验分享|测试工程师转型测试开发历程

聊聊UI自动化的PageObject设计模式

细读《阿里测试之道》

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

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

相关文章

CAD二次开发(6)-用户交互之选择集

1. 简单测试 测试让选中的图形描红 [CommandMethod("SeleDemo")]public void SeleDemo(){Database db HostApplicationServices.WorkingDatabase;Editor ed Application.DocumentManager.MdiActiveDocument.Editor;PromptSelectionResult psr ed.GetSelection();…

如何学到数据库从入门到入土(MySQL篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接…

mars3d的V2版本的Video2D与V3版本的Video2D实现数据快速迁移

场景: 目前是v2和v3的两个相机视角的不同格式,在Mars3d的V2的旧数据想可以快速迁移到V3版本。 V2版本的数据: {"camera": {"fov": 1.0471975511965976,"dis": 20,"stRotation": 0,"showFrust…

基于小波分析和机器学习(SVM,KNN,NB,MLP)的癫痫脑电图检测(MATLAB环境)

癫痫是一种由大脑神经元突发性异常放电导致的大脑功能性障碍疾病。据世界卫生组织统计,全球约有7000万人患有癫痫。癫痫患者在发病时呈现肌肉抽搐、呼吸困难、意识丧失等症状。由于癫痫发作的偶然性,患者极有可能在高空、驾驶、游泳等危险情况下发病并丧…

2024最新 Jenkins + Docker实战教程(二) - Jenkins相关配置

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…

C语言代码错误(一)

今天在写选择排序代码时&#xff0c;在测试数据发现不能显示结果 1、代码如下&#xff1a; #include <stdio.h>int main(void) {int i, j; // 循环变量int MinIndex; // 保存最小的值的下标int buf; // 互换数据时的临时变量int n;printf("你想输入多少个数据n:\n…

C++之lambda函数与std::bind区别及用法实例(二百八十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

LabVIEW如何实现多张图拼接

在LabVIEW中实现相机多次拍摄进行拼接的过程&#xff0c;可以分为以下几个步骤&#xff1a;设置相机参数、控制相机拍摄、图像处理与拼接、显示和保存结果。以下是一个详细的实现方案&#xff1a; 1. 设置相机参数 首先需要配置相机的参数&#xff0c;例如分辨率、曝光时间、…

Java Swing + MySQL图书借阅管理系统

系列文章目录 Java Swing MySQL 图书管理系统 Java Swing MySQL 图书借阅管理系统 文章目录 系列文章目录前言一、项目展示二、部分代码1.Book2.BookDao3.DBUtil4.BookAddInternalFrame5.Login 三、配置 前言 项目是使用Java swing开发&#xff0c;界面设计比较简洁、适合作…

react-d3-tree:React组件创建交互式D3树形图

在这里插入代码片import React from "react"; import ReactDOM from "react-dom"; import Tree from "react-d3-tree";import "./styles.css";const myTreeData [{name: "Gaurang Torvekar",attributes: {keyA: "val …

基于Llama 3搭建中文版(Llama3-Chinese-Chat)大模型对话聊天机器人

前面两篇博文&#xff0c;我们分别在个人笔记本电脑部署了Llama 3 8B参数大模型&#xff0c;并使用Ollama搭建了基于 Web 可视化对话聊天机器人&#xff0c;可以在自己电脑上愉快的与Llama大模型 Web 机器人对话聊天了。但在使用过程中&#xff0c;笔者发现Llama大模型经常出现…

OpenWrt U盘安装使用 详细教程 x86/64平台 软路由实测 系列一

1 官方稳定 版:OpenWrt 23.05 OpenWrt Downloads #根据实际情况选择 PC支持uefi,选择版本&#xff1a;https://downloads.openwrt.org/releases/23.05.3/targets/x86/64/openwrt-23.05.3-x86-64-generic-ext4-combined-efi.img.gz 2 rufus 制作U盘启动 3 制作好的U盘,接入主…

基于集成经验模态分解的心电信号降噪和基于希尔伯特变换的R峰检测(MATLAB R2018)

近年来&#xff0c;心脏病已成为危害人类健康最常见的疾病。为了有效预防心脏疾病的发生&#xff0c;往往需要更加准确地采集与诊断心电信号&#xff0c;以便于更好地反映心脏情况。心电信号作为人体生理信号&#xff0c;对于识别心脏异常和心脏疾病具有重要的参考价值。心电信…

ROS基础学习-ROS通信机制研究

研究ROS通信机制 研究ROS通信机制 0.前言1.话题通信1.1 理论模型1.2 话题通讯的基本操作1.2.1 C++1.2.2 Python0.前言 机器人是一种高度复杂的系统性实现,在机器人上可能集成各种传感器(雷达、摄像头、GPS…)以及运动控制实现,为了解耦合,在ROS中每一个功能点都是一个单独的…

从File类开始,学习Java文件操作

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

windows安装SQL Server

1、下载 下载网页&#xff1a;SQL Server 下載 | Microsoft 2022版下载地址&#xff1a;https://go.microsoft.com/fwlink/p/?linkid2215158&clcid0x404&culturezh-tw&countrytw 下载结果&#xff1a;SQL2022-SSEI-Dev.exe 打开选第三个&#xff0c;下载介质&…

自定义Linux命令,显示docker镜像、容器信息

1、修改环境变量&#xff08;仅对当前用户有效&#xff09; vim ~/.bashrc2、给命令取别名 alias dpsdocker ps --format "table{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}" alias disdocker images#保存并退出 :wq3、让配置重新生效 source ~/.bashrc4、测试&…

【docker】仓库harbor的部署

harbor介绍 Harbor 是一个用于存储和管理 Docker 镜像的开源仓库。它提供了一系列的功能&#xff0c;比如用户管理、访问控制、镜像管理、日志审计和安全扫描等。Harbor 可以作为私有仓库来使用&#xff0c;也可以与公有仓库&#xff08;如 Docker Hub&#xff09;集成使用。 …

python数据分析——apply 1

参考资料&#xff1a;活用pandas库 apply是指把函数同时作用于DataFrame的每一行或每一列。类似于编写一些跨每行或每列的for循环&#xff0c;并同时调用apply函数。 1、函数 函数是对python代码进行分组和复用的一种方法。如果某段代码会被多次使用&#xff0c;并且使用时是需…

优化FPGA SelectIO接口VREF生成电路

引言&#xff1a;FPGA设计中使用了各种PCB SelectIO™接口VREF生成电路。有时即使在以前的设计中已经成功的在电路板上设计了VREF生成电路&#xff0c;也会在VREF引脚上发现大量噪声&#xff08;200–400mV&#xff09;。大量VREF噪声的存在可能导致高性能SelectIO接口&#xf…