作者 | 包丹珠 上海控安产品总监
版块 | 鉴源论坛 · 观模
社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区”
“软件单元测试真的有必要吗?”专题将分为上下两期,深度详解软件单元测试的重要意义,分享目前行业内进行的单元测试探索与实践。本文着重探讨单元测试的重要性及其正面临的困境,并介绍功能安全标准中罗列的单元测试方法。
01
前言
元宇宙虚拟世界被大家探讨得越来越深入。随着Facebook公司更名为Meta,这场未来之战已经吹响号角。元宇宙是虚拟的一个世界,核心是软件创造的世界。那么,软件定义一切对普罗大众来说将不再是个空想,而是正在抵达的不久的将来。
如果在读的你对软件还没有成熟的认知,对产品体验始终停留在有形的实体的执念上的话,那么你要注意更新自己的认知了,要尽快做好抓住虚拟未来各种机遇的准备哦。
如果说软件成为未来世界的基础,那么软件单元将是基础中的基础。当我们在谈论软件单元测试时,我们实际在谈的是如何打好软件基础。让我们把视角从未来切回到当下,一起来探讨目前关于软件单元测试所面临的实际困境,以及如何正确面对这些困境,从而期待为基础的软件研发过程做好坚实的技术支撑。
02
灵魂拷问:单元测试耗时费力、成效模糊难以直观预见,功能安全标准为何将其作为关键过程进行要求?
软件单元测试不是一个新的概念。不论是工业领域,还是金融、互联网的软件从业人员,相信对单元测试都或多或少有一定的认知,甚至已经产生了相关的实践。随着IEC 61508功能安全标准体系的不断扩充发展,安全攸关的领域最先对其引起了重视。
在快速软件交付为王的背景之下,研发人员对单元测试的态度是认为基础且重要,但是存在较大负担的。
1. 首先,单元测试任务琐碎繁重,会占用大量时间。通常情况下,对于一个小型10万行规模的软件,大约需要设计10000条用例去进行测试。基本上,在没有工具支撑的情况,需要消耗同写代码同样的时间去撰写测试驱动并测试;在有工具支持的情况会有所缓解,但也至少多花费一半的时间。
2. 其次,单元测试预期能产生的效果不是能立即直观体现出来的。软件单元测试是在软件开发过程中进行,软件还未集成在一起,那么对于单元测试发现的问题不能立即直观地体现在软件的整体功能上的,因此一开始缺乏直观可见的对比证据来说服开发人员或者领导单元测试的重要意义。也就是说,单元测试的好处就好比保养,在软件开发阶段是不能直观体现出来的,需要到了软件投入使用之后,经过同类对比,才能真正的直观体现出来。举个例子,两个负责不同模块的程序员,一个总能快速完成开发,另一个就要花较长的时间才能给出。从效率上讲似乎快的能力更强。但是当给到客户之后,你会发现完成快的模块反馈出来奇奇怪怪的各种问题,需要很大的后期维护成本;慢的问题相对较少,渐渐地变得没有新问题反馈出来。整体上来讲,快的反而要花更长的时间,并且还消耗了口碑,投入了更多关系维护成本。
3. 再次,测试标准如何定义,何种程度可以认为是测试充分的是一个很关键的问题。单元测试通常会设定一些覆盖率的指标,比如要求达到语句、分支覆盖率100%。但是做到这些覆盖率的指标就能证明没有问题了吗?很显然不能,这是测试理论本身的通病,即只能发现问题,不能证明没有问题。但这不是不进行单元测试的理由。这些指标要求是基础要求,能够完成这些指标,软件的质量会有一个质的提升。
4. 最后,当详细设计文档不完善时,研发人员缺乏明确的测试依据,导致工作难以开展。软件单元测试需要比较强的前置条件,即需要有详细的软件设计文档作为测试的需求依据。然而,这个前置条件本身如果不具备的话,那么就失去了做单元测试的基础。这种情况下,企业可能需要考虑先下决心建立软件质量保证的流程体系,再来进一步细化具体的单元测试工作(敏捷开发对文档没有强制要求,敏捷开发依赖的是成员高度的共识和信息通畅,低文档,高沟通。单元测试依据的内容本质上没有区别,只不过更多通过沟通结果来作为无形的依据。这个有机会可以深入探讨)。
因此,耗时费力,成效模糊难以直观预见是软件研发人员进行单元测试时需要排除的困难,而不是单元测试没有意义的原因。在来谈困难以及解决办法之前,我们应当先明确什么是对的。单元测试是很有必要的,功能安全标准制定者将其作为关键的软件验证过程是有很高的指导意义的。软件单元测试可以有效提升软件整体的质量和健壮性。除非极简单的软件系统,单元测试如果不充分,做再多的系统测试也是不够的,就好比扫雷不彻底,始终有触雷的隐患。
03
单元测试到底在做什么,要做到什么程度?
软件单元是软件最小的设计单位,软件单元的实现需要依据软件设计需求。根据IEC 61508的要求,即“每个软件模块应当按软件设计所规定的进行测试。这些测试应当表明每个软件模块将执行其预期功能,且不会执行非预期功能”。因此单元测试的核心目标是确保软件单元同软件设计的一致性。
为了确保一致性,功能安全标准列出了多种单元测试的方法。其中等价类测试和基于结构的测试对于单元测试来说是充足的。边界值测试和控制流程分析可以有效降低测试用例的数目。用尽可能少的用例来完成充足的测试是单元测试的最高追求。
1. 等价类测试
等价类测试指的是将测试的输入分成几类,每一类提供一个有代表性的输入进行测试即可。这种方法免除了穷尽所有可能性的不切实际的测试方法,通过减少测试用例的数目到可接受的程度,可以有效提升测试的成效。
等价类划分的方法有两种:
1)从设计需求推导等价类。可以从输入数据的角度,或者从输出数据的角度进行划分。
2)从程序的内部结构推导等价类。可以根据静态分析后的程序路径来进行划分。
2. 基于结构的测试
基于结构的测试,又称白盒测试,是基于代码来进行测试的一种技术。该测试方法关注代码的内部结构,需要研发或测试人员详细了解代码的内部结构。同时测试充分性有确定的覆盖度量方法,可以通过系统性的增加测试用例来提升覆盖率。基于结构的测试方法有多种,比较基础的包含以下四种。
1)调用图覆盖。也叫做入口点测试,或者调用覆盖测试。调用图指的是子程序调用的树形图。该测试方法的目的是确保所有的子程序都被至少调用一次。
2)语句覆盖。语句覆盖是最基础的测试方法,需要确保所有的语句都至少被测试一次。该方法较为简单,然而对于测试来讲精确度不是很高。
3)分支覆盖。分支覆盖指的是对于代码的分支结构,需要确保所有的分支都被至少测试一次。该方法相对语句覆盖,其测试精确度有较明显的提升。
4)MC/DC 覆盖。修正的条件判定覆盖,其针对的是程序的分支结构,并且影响分支走向的是多种条件的组合判定。该方法需要确保所有条件均能够至少影响分支走向一次。该测试方法相对分支覆盖,其测试精确度有了更高的提升。并且可以确保以最少的用例数目,达到更精确化测试的目的。
以上是IEC 61508功能安全标准对基于结构测试的基础推荐方法。其中每一种测试覆盖方法,均要达到100%的覆盖。更高级的基于结构的测试方法还有LCSAJ线性代码序列和跳转测试、数据流、基本路径等覆盖测试方法。有兴趣的读者可以进一步研究,并结合代码特征灵活进行选用。
3. 边界值测试
通常软件错误通常会出现在软件参数的边界点。边界值测试要求测试应涵盖等价类的边界和极端情况,同时还要检查需求中的边界同程序中的边界是否一致。通常情况下,零值特别容易引起错误,需要特别注意零值的测试。如:
1)除零错误
2)空ASCII字符
3)空栈或者表元素
4)满矩阵
5)0表项
4. 控制流分析
控制流分析是为了寻找程序结构潜在错误的一种测试方法。它通过采用静态测试技术来分析程序的控制流程图,然后进一步分析控制流程图来发现不良的程序实践。通过控制流程图分析,可以发现以下问题。
1)不可达代码。也被称为死代码,指的是永远不会被触发执行到的代码。
2)打结的代码。良好的代码通常可以将其控制流程图简化为一个单节点,相反的,不良的代码则会简化为一个环状结构。
因此,软件单元测试是一项有明确的测试方法指导,并且具备充分性度量的标准的一项测试工作。在设定测试方法时,可以根据待测软件的质量或安全要求级别,选取适合的测试方法组合。
在下一期“软件单元测试真的有必要吗?(下)”中,将深入探讨单元测试过程中,如何在保质保量完成测试任务的同时,缩减时间成本、提高测试效率,并分享目前行业内的实践经验以及相关自动化测试工具。