为测试而设计——软件可测试性为扩展
发表时间:2023-10-26 06:02:37
文章来源:炫佑科技
浏览次数:137
菏泽炫佑科技
为测试而设计——软件可测试性为扩展
随着云原生技术的加速普及和快速发展,软件系统的规模和复杂性不断增加。 相应地,在软件开发过程中,测试设计(for)、部署设计(for)、监控设计(for)、扩展设计(scale)和故障设计(for)变得越来越重要,甚至成为衡量软件组织核心研发能力的主要标尺。
本文围绕“为测试而设计”的概念,以软件可测试性()为主线,阐述了软件可测试性的各个方面以及软件组织在这个方向上的一些*佳实践和探索。
软件可测试性在软件开发和质量保证中起着至关重要的作用,是实现高质量、高效交付的基础。 可测试性差会直接增加测试成本自动化软件开发,并且难以验证测试结果,进而导致工程师不愿意做测试或延误测试活动。 这些都违背了“持续测试并以低成本尽早发现问题”的规则。 为此,我们有必要对可测试性进行一次深入而简单的讨论。 主要内容包括以下五个方面。
1 可测试性的定义
软件可测试性是指在一定的时间和成本内设计和执行测试以发现软件问题、发现故障、隔离和定位故障的能力。 不同的组织对可测试性有不同的定义(图 1)。 我认为本质是一样的。 他们都谈论软件系统测试的难易程度,或者软件系统被确认的能力。 。
我个人*喜欢的定义是 James Bach 的版本:“可测试性是测试计算机程序的容易程度。”
图 1:可测试性的各种定义
测试设计能力,创造性地想象各种可能性,设计相应的场景是每个软件测试人员的核心技能,但如何根据测试设计构建所需的测试条件,如何高效地执行测试,以及在测试过程中如何进行测试测试执行过程实时观察和验证结果是可测试性需要解决的问题。
2 可测试性差带来的问题
很多人会觉得可测试性似乎是一个新的命题。 在软件测试发展的很长一段时间里,这个概念似乎并没有被广泛提及。 那是因为以前的软件测试是一个粗糙的黑盒模型,测试团队和开发团队是分开的。 测试工程师往往只在开发后期介入,测试始终处于被动状态。 而且很多测试和验证都偏向于黑盒功能,因此可测试性的矛盾并没有凸显出来。 但在测试左移、开发者自测试、测试与开发一体化、精准测试广泛普及的今天,这种粗暴的黑盒验证已经不能满足软件的质量要求。
如果继续忽视可测试性,不从源头上重视可测试性,就会导致开发过程中出现系统不可测试或者测试成本过高的困境。 可以说,忽视可测试性正在积累技术债务。 更何况,现在流行的整个流程都离不开测试。 测试成为连接持续集成和持续发布(CI/CD)各个阶段的“连接器”。 如果可测试性不好,整个持续集成和发布的效率也会降低。 将会受到很大的影响。
为了帮助大家更好地理解可测试性,这里以一些实际的可测试性问题作为出发点。
GUI测试级别:
接口测试级别:
代码级别:
一般级别:
可见,可测试性问题不仅仅出现在端到端的功能测试层面,还出现在接口测试和代码级测试层面。 可测试性对于自动化测试的实施成本也至关重要。
类似的例子还有很多,比如不受控的触发条件、时间触发的逻辑、难以获取的条件、调用链接的获取以及大量的外部系统依赖等等,限于篇幅,我就不一一赘述了。在这里由一位。 。
3 可测试性的三个核心观点
在正式讨论可测试性的技术细节之前,有必要和大家统一一下可测试性的核心概念。 我认为可测试性有三个核心观点(图2)。
图 2:可测试性的 3 个核心视角
a) 可测试性是设计所决定的
毫无疑问,可测试性不是固有的,而是被设计和实现的。 可测试性必须明确设计并正式纳入需求管理中。 在研发团队内部,测试架构师应带头推动可测试性,并与软件架构师、开发工程师和测试工程师达成一致。 测试工程师和测试架构师应该是可测性需求的提出者,并负责可测性解决方案的评估和验证。 在研发过程中,可测试性评估应尽早开始,通常从需求分析和设计阶段开始,贯穿整个研发过程。 因此,可测试性不再是测试工程师的责任,而是整个研发团队的责任。
b) 提高可测试性可以节省研发成本
良好的可测试性意味着测试的时间成本和技术成本都会降低。 还可以提高自动化测试的可靠性和稳定性,降低自动化测试的成本。 今天对可测试性的初始投资将导致后续测试成本的显着降低。 今天多花一块钱,将来可以省十块钱,这再次证明了“很多时候,选择比努力更重要”的观点。
c) 关注可测试性可以提高软件质量
具有良好可测试性的软件必须具有高内聚、低耦合、定义良好的接口和明确的行为意图的设计。 当准备编写新代码时,问自己一些问题:“我将如何测试我的代码?我将如何编写自动化测试用例来验证代码是否正确,同时*小化其运行环境?” 如果您不能回答这些问题后,请重新设计您的界面和代码。 当您开发软件时,请始终问自己“我将如何验证软件的行为是否符合预期?” 并愿意精心设计软件来实现这一目标。 作为回报,您将获得一个结构良好的系统。
*后我想说的是“质量是奢侈品,可测试性是奢侈品中的奢侈品”。 让研发团队重视可测试性是很困难的。 根本原因是研发团队“不够痛”。
长期以来,测试和开发一直是两个独立的团队。 开发工程师往往更关注功能的实现。 充其量,他们会关注一些与性能、安全性和兼容性相关的非功能性需求,对于可测试性基本上没有优先考虑。 级,因为测试工作不是开发工程师自己完成的,开发工程师根本感受不到可测试性的价值。 虽然测试工程师遭受着可测试性的各种折磨,但他们也苦于处于软件开发生命周期的下游而无能为力,因为很多可测试性需求需要在设计阶段就考虑和实现。 很多事情在测试阶段就已经太晚了。
很多时候,你不想改变,因为它不痛,你不想改变,因为它还不够痛。 只有真正经历过痛苦,你才会知道改变的可贵。 因此,应该允许开发工程师自己承担测试工作。 这样,开发工程师就会亲身感受到可测试性的重要性和价值,进而在设计和实现阶段赋予系统更好的可测试性。 这是一个良性循环。 系统的整体可测试性始终能够处于较高水平。 这其实就是开发者自测能够带来的好处。 关于开发者自测试这个话题,可以关注我之前在InfoQ上写的一篇热门文章《开发应该自己测试吗?怎么做?》。
4 可测试性的四个维度
可测试性的分类有许多不同的版本。 例如,James Bach提出的“实际可测试性”模型(图3),提出的SOCK可测试性模型(图4),提出的“可测试性设计清单”模型等。
图3:James Bach提出的“真实可测试性”模型
( 的 )
尽管各种分类方法的切入点不同,但其本质是相同的。 在这些模型的基础上,我做了一些归纳和总结,并将其定义为四个维度:可控性、可观察性、可追溯性和可理解性(图5)。 下面我们依次讨论一下。
图 5:可测试性的 4 个维度
可控性
可控性是指程序的行为、输入和输出是否容易控制,被测系统的状态是否能控制到测试条件的要求。 一般来说,一个可控性好的系统一定更容易测试,更容易实现自动化测试。 可控性一般体现在以下几个方面:
可观测性
可观察性是指程序的行为、输入和输出是否易于观察。 一般指通过某种手段从外部获取系统内部重要状态和信息的难易程度。
任何操作或输入都应该有一个预期的、明确的响应或输出,并且这个响应或输出必须是可见的。 这里的“可见”不仅指运行时可见,还包括维护时可见和调试时可见。 同时,在时间维度上,“当前”和“过去”都应该是“可见的”、可查询的。 “”和“”意味着“不可发现”,可观测性较差。 影响可测试性。
“可见性”的前提是输出。 为了提高可观察性,应该有更多的输出,包括分层事件日志()、调用链路跟踪信息()、各种聚合指标(),还应该提供各种可测试性接口。 获取内部信息并报告内部系统自检信息为测试而设计——软件可测试性为扩展,以确保影响程序行为的因素可见。 此外,有问题的输出必须易于识别,无论是通过自动日志分析还是界面突出显示,以便于发现。
关于“更多输出”的概念,我们有一个概念指标DRR(/Range Ratio)可供参考。 DRR可以理解为输入数量与输出数量的比率。 DRR用于衡量信息丢失的程度。 DRR越大,信息越容易丢失,错误越容易被隐藏,可测试性越低。 因此,要降低DRR,在输入数量不变的情况下,必须增加输出数量。 输出参数越多,可以获得的信息就越多,也就越容易发现错误。
接下来我们来说说可观测性和监控的关系。 监控告诉我们系统的哪些部分不工作,可观察性告诉我们为什么不工作的部分不工作,所以我认为监控是可观察性的一部分,可观察性是监控的超集。 两者的区别主要体现在主动发现()问题的能力上。 可以说,主动发现是可观测能力的关键。 我们今天所说的可观测性正在从过去的“被动监测”转向“主动发现和分析”。
通常我们将可观测能力分为五个级别(图6),其中报警()和应用概览()属于传统监控的概念。 由于警报往往是由明显的症状和表象触发的,随着系统架构变得更加复杂以及应用程序转向云原生部署方式,没有警报并不意味着系统没有问题。 因此,系统内部信息的获取和分析必然变得非常重要。 这部分能力主要体现在调试()、分析()和依赖分析()上。 这三者体现了“主动发现和分析”的能力,并且逐层递进:
首先,无论是否发生报警,都可以利用主动发现能力来诊断系统运行情况,并通过指示灯呈现系统运行的实时状态;
其次,一旦发现异常,逐层深入定位问题,必要时进行性能分析,检索详细信息,建立深入洞察;
第三,检索模块之间的交互状态,通过链路追踪构建整个系统的“上帝视角”。
图6:可观测性和监控之间的关系
除了警报和故障排除之外,主动发现能力的目的还包括获取全面的数据和信息以建立对系统的深入了解,而这些知识可以帮助我们提前预测和预防故障。
*后说一下可观测性和可控性的关系。 可观测性不仅可以观察系统的输出是否满足设计要求,还影响系统是否可控。 系统必要的状态信息在系统测试控制阶段起着决定性的作用。 如果没有准确的状态信息,测试工程师无法确定是否进行下一次控制更改。 如果状态变化都无法控制,那还谈什么可控呢? 因此,可观测性和可控性是相辅相成、缺一不可的。
可追溯性
可追溯性是指系统的行为、事件、操作、状态、性能、错误、调用链路等是否可以轻松追溯。
溯源帮助你成为“系统侦探”,可以帮助你成为自己系统的福尔摩斯。 可追溯性只需体现在以下几个方面:
云原生时代,日志()、链路跟踪()和指标()的全面融合是该领域的主要发展方向,旨在统一三者,实现数据的互操作和互操作,改变孤立的做法。 、信息孤岛问题。
易懂
可理解性是指被测系统的信息是否容易获得,信息本身是否完整、易于理解。 比如被测对象是否有文档,文档本身的可读性和时效性有保证。 共同的可理解性包括以下几个方面:
5 不同级别的可测试性和工程实践
不同级别有不同的可测试性要求。 下面我们将从代码层面、服务层面和业务需求层面分别进行讨论。
a) 代码级可测试性
代码级可测试性是指为代码编写单元测试的难易程度。 对于一段被测试的代码,如果为其编写单元测试非常困难,需要依赖很多“技巧”或者单元测试框架和Mock框架的高级功能,这往往意味着:代码实现不够合理,代码的可测试性不好。 如果你是一名高级开发工程师,一直有编写单元测试的习惯,你会发现编写单元测试本身并不困难。 相反,编写具有良好可测试性的代码是一件非常具有挑战性的事情。
有许多违反代码可测试性的反模式,常见的包括以下几种:
· 无法模拟依赖组件或服务
· 代码包含未决的行为逻辑
· 滥用可变全局变量
· 滥用静态方法
· 使用复杂的继承关系
· 高耦合的代码
· I/O和计算没有解耦
为了方便理解,我们用“不能依赖Mock的组件或服务”来给大家举个例子,以便大家更好地理解什么是代码级可测试性。
以下是示例的测试代码(图7)。 该类是电子商务系统的抽象、简化的交易类。 用于记录每笔订单交易的状态。 类中的()函数负责执行传输操作。 钱从买方的钱包转移到卖方的钱包。 实际的转账操作是通过()函数中调用RPC服务来完成的。
图 7:被测代码:类
现在为其编写一个单元测试,如下所示(图 8)。
图8:类中()函数的单元测试
这个单元测试的代码本身很容易理解。 无非就是提供参数来调用函数。 不过,如果想让这个单元测试顺利运行,就需要部署服务。 首先,建立和维护成本相对较高。 其次,还要求确保将伪造的数据发送到服务后,能够正确返回我们期望的结果,完成不同路径的测试覆盖率。 而且测试的执行需要经过网络,时间较长,并且会出现网络中断、超时、服务不可用的情况。 ,会直接影响单元测试的执行,所以严格意义上来说,此类测试不再属于单元测试的范畴,而更像是集成测试。 所以我们需要使用Mock来实现依赖解耦,用一个“假”的服务来替代真实的服务,而这个“假”的Mock服务需要完全在我们的控制之下,来模拟并输出我们想要的数据,从而控制执行测试路径。
为此,我们可以通过继承该类并重写其中的()函数,轻松构建如下Mock(图9)。 这允许 () 返回我们想要的任何数据,而无需实际通过网络进行通信。
图 9:模拟
但是当你尝试用 One 和 Two 替换代码中的真实值时,你会发现,由于它是在 () 函数中通过 new 创建的(图 7 代码第 6 行),所以我们无法动态地对其进行替换,这是一个典型的代码可测试性问题。
为了解决这个问题,需要对代码进行适当的重构。 这里会用到依赖注入。 依赖注入是实现代码可测试性的*有效手段。 它可以将对象的创建逆向上层逻辑,在外部创建好后,注入到类中。 具体代码实现如下(图10)。
图 10:使用依赖注入解决代码可测试性问题
这样,单元测试就可以轻松地用 One 或 Mock 替换(图 11)。
图 11:重构后的单元测试
以上是一个使用依赖注入实现Mock并解决可测试性问题的实际案例。在代码级可测试性方面,早期有很好的实践。 构建了一套工具来全面评估代码的可测试性并提供分析报告(图12),这有点类似于代码静态检查。 主意。 不幸的是,它不再被维护。 详细信息请参考
。
图 12:报告
b) 服务水平可测试性
服务级别的可测试性主要针对微服务。 相对于代码级可测试性,服务级可测试性更容易理解。 一般来说,服务水平可测试性主要考虑以下几个方面:
· 界面设计的契约化程度
· 界面设计文档的详细程度
· 私有协议的详细设计
· 服务操作的隔离性
· 业务扇入扇出尺寸
· 部署服务有多容易
· 获取服务配置信息有多容易
· 服务内部状态可控
· 构建测试数据有多容易
· 易于验证服务输出结果
· 验证服务的向后兼容性有多容易
· 轻松获取和聚合服务合同
· 服务资源使用情况的可观测性
· 模拟内部异常是多么容易
· 模拟外部异常是多么容易
· 跟踪服务呼叫链接有多容易
· 内置测试(BIST)的实施水平
· …
c) 业务需求级别的可测试性
业务级的可测试性是*容易理解的,也是大家接触*多的。 一般来说,业务级可测试性可以进一步细分为手动测试的可测试性和自动化测试的可测试性。 业务需求层面的可测试性有以下典型场景:
· 登录过程中的图片验证码或短信验证码
· 硬件U盾/USB Key
· 触摸屏应用的自动化测试设计
· 第三方系统的依赖与模拟
· 业务测试流量隔离
· 系统不确定性弹出框
· 验证非回波结果
· 可测试性和安全性之间的平衡
· 业务测试分段执行
· 业务测试数据构建
· …
总结
本文系统地讨论了可测试性的定义,讨论了可测试性差带来的问题,并给出了可测试性的三个核心观点和四个维度。 *后从代码层面、服务层面和业务需求层面讨论了可测试性的例子和关注点。
炫佑科技专注互联网开发小程序开发-app开发-软件开发-网站制作等