写点什么

只擅长构建软件是不够的,我们必须擅长构建可测试的软件 | QCon

  • 2022-09-20
    北京
  • 本文字数:7138 字

    阅读完需:约 23 分钟

只擅长构建软件是不够的,我们必须擅长构建可测试的软件 | QCon

我坚信下面这个说法是对的:


在验证具有同等复杂性的软件系统的难度方面,可能存在数量级的差异。


这种说法并非夸大其词。虽然我们无法精确地衡量验证难度(比较两个不相关系统的相对复杂性具有高度的主观性),但我仍然认为,整体工作量差异可能是数量级的。如果验证一个系统需要一个小时,那么验证另一个系统可能需要 100 多个小时。这种信念来自于我在过去的 22 年里观察并参与了数十家公司数百个系统的开发和测试。


有人会觉得这怎么可能?软件的设计和实现可能很糟糕,但这只会导致 50% 或最多 100% 的难度差异,不是吗?怎么可能将软件实现得如此糟糕,以致于出现数量级的差异?


我希望我能介绍三种保证让你的系统更可测试的设计模式,然后详细介绍每一种模式。然而,我们的问题没那么简单。如果创建可测试的软件系统像使用设计模式一样容易,那么我们都已经在享受高度可测试的软件了,我也不需要写这篇文章了。


相反,我将试图说明这种巨大的难度差异确实存在,以及为什么它很重要。然后我将谈论为什么质量保证(即整个测试软件的规程)不仅存在不足,实际上还会使问题变得更糟。这将催生针对测试质量的一种简单但不一样的观点,这种观点将自然而然地促进更多可测试的系统和更可预测、更有效的软件交付的出现。


不可测试软件的表现


为了说明可测试性的巨大差异,我会详细描述两个分别位于可测试性频谱两端的软件系统。首先会描述整个架构、系统设计层、系统中每个组件的实现、使用的技术栈、与其他系统之间的边界交互、数据模型、持久化策略,可能还有更多其他东西。然后,我将详细介绍验证策略、测试数据管理、为不同类型的测试实现自动化的时间和做法,以及可以在 CI 管道的哪里运行。接着,我将说明架构、设计、实现和技术栈决策的细微差别,以及它们如何聚在一起创造出令人沮丧的验证体验。将这两个等价的系统——一个非常不可测试,另一个非常可测试——放在一起,希望能让你相信可测试性之间是存在数量级差异的。


上述的每一项描述其实都可以写成几十页,但此处幸运的是这是一篇博文,而不是一本书。


因此,我必须采用一种更简洁的策略:我将描述各种难以测试的软件的症状,你可能已经遇到过的挫折和挑战,并做出了退步,让你相信某些软件更具可测试性。我希望通过对这些表现和经验的反思,让你了解它们并不是每个人在正常软件交付中都要面对的挑战,它们只是表明某些事情出现了问题的坏味道。


所有这些都是真实的例子,来自真实团队构建的真实软件。以下是一些难以测试的软件的症状,排名不分先后。


测试数据准备和维护噩梦:准备、清理和管理测试数据异常复杂,并消耗大量的验证时间。测试数据是一个很大的类别,包括测试所依赖的每一种状态:数据库特定的行和值、具有特定权限的特定用户、股票中具有特定属性的特定项、配置值等。管理这些测试数据是测试的一个重要组成部分,但在难以测试的系统中,确定和有效地控制数据是非常具有挑战性的,甚至是完全不可能的。


不幸的是,即使在架构良好的系统中,对于测试人员来说,根据测试需要管理系统状态和测试数据也是非常具有挑战性的。


回想一下,有多少次,你花了几个小时甚至几天的时间,只是为了准备数据来重现一个棘手的 Bug 或一个罕见但关键的边缘情况?在一个系统中测试一个东西需要花费数天时间,而在另一个系统中同样的测试可能只需要数小时,这就是我所说的数量级差异的一个例子。令人沮丧的是,数据管理难只是造成这种情况的一个原因,而且是难以测试的系统的常见症状。


副作用(Side Effect)执行验证:当测试不能直接确定结果,但必须依赖验证对象不那么可信、不那么明确的副作用时,就会出现这种症状。例如,如果系统处于 ABC 状态,我们执行 XYZ 动作,那么它应该会导致结果 R。不幸的是,我们不能直接观察到 R,所以我们必须找到 R 的副作用,而不是 R 本身。


根据副作用执行验证经常出现在可观测性较差的系统中。然而,仅仅因为一个系统的架构具备可观测性,并不意味着从验证的角度来看它也是可观测的,而且也不会受到这个问题的影响。例如,许多系统都是由高度可观测的独立组件组成的,在测试时需要将这些组件之间的事件关联起来,但不存在这样的机制。虽然单个组件是可观测的,但作为一个整体的系统是不可观测的,这严重影响了可测试性。


一个更简单并更常见的例子是在测试时需要通过抓取系统日志来读取系统状态。可测试的系统不要求测试通过获取日志文件来验证其行为,这么做会导致一个系统中的一些简单而可靠的事情在另一个系统中变得非常乏味和不可靠。


对人工测试的需求:如果在测试时必须将自定义功能引入到生产代码中,就会出现这种情况。例如,服务端点、数据注入 / 收集机制或配置选项。这些代码存在的唯一理由是用它们来支持测试,这么做是必要的,因为实际的系统非常难以测试。虽然加入一些小测试挂钩不一定总是坏事,但为了支持测试被迫加入大量的人工测试是难以测试的软件的一个症状。


畸形的测试金字塔:当测试套件由于系统的特质而被迫过度依赖某种类型的测试来进行大多数验证时就会出现这种症状。这一问题的常见形式是 UI 驱动的端到端测试与其他较小的测试(通常称为“倒金字塔测试”)的数量不成比例。


这并不是因为自动化系统需要进行端到端 UI 测试,而是因为被测试系统的某些方面不得不通过 UI 进行强制验证,可能是糟糕的 API 设计、专有的传输协议、糟糕的类设计、过于复杂的数据模型或其他的原因。就像透过钥匙孔窥视大房间一样,测试只能透过一个小透镜访问系统,它们被系统的特质所阻碍,无法碰到更合适、更有效的交互点。


必须部署、设置有效的大型(可能是共享的)测试环境来验证行为——因为所有的测试都是端到端测试,当某种行为只需要系统的一小部分时,这就是一种浪费,也是难以测试的软件的常见症状。


测试环境稀缺 / 资源争夺:当底层架构或系统的其他方面导致将系统复制到测试环境或快速部署新环境变得具有挑战性或不可能时,就会出现这种症状。


例如,很多系统中可能包含了来自 SAP 或 Oracle 等供应商提供的企业 ERP 或 CRM 软件实例。这些组件代表了整个系统的核心功能,但因为实例受限(由于许可或自定义硬件需求),不得不在环境之间强制采用多租户方法(即跨测试环境共享 ERP 系统的实例)。虽然我们可能无法消除对第三方组件的依赖,但任何阻止低成本和快速部署新环境的行为都会降低可测试性。


此外,在环境之间共享组件实例引入了各种新的测试数据管理问题,这是对可测试性的另一个负面影响。


时间依赖:当测试或自动化测试以某种方式与系统的真实时钟相关联时,就会出现这种症状。例如,假设一个系统在一天的特定时间批处理一些数据,并且不能人为地触发处理事件,或者如果被触发,将对系统的其他区域产生不必要的影响。因此,对这个处理功能的任何测试都必须确保它与真实世界的时间是同步的。


这个例子看起来似乎不是很自然,而且显然是一种糟糕的架构,但对于大型的企业软件系统来说,与非自动化过程或与关联了时间的过程发生交互是很常见的,这些交互严重阻碍了可测试性。


我想那些有验证现代软件经验的人应该会认同,想想你们有多少次碰得头破血流,只是为了尝试验证系统里的那些看起来很容易的部分!或者你们花很多时间调试一个失败的测试用例,只是为了重新运行它,并让它通过,却不知道发生了什么。你们应该对难以测试的软件的痛点深有体会。


许多专业人士可以接受这些情况,因为它们太常见了。他们假设处理不可管理的测试数据、使用有限或共享的测试环境、根据副作用执行验证或依赖自定义测试挂钩是必要的,或者至少是正常的。他们只验证过难以测试的软件,所以这已经成为了一种期待,成为一种可接受和忍受的东西。他们从来没有验证过真正可测试的系统,所以不知道拿什么与之对比。


然而,导致这些症状的根源并不是测试人员或测试方法,并不是因为他们使用了错误的工具,或者他们不够聪明。根本原因是系统难以测试。他们被要求解决这些棘手的问题,他们不得不去解决。他们认为这是他们的工作,并尽他们所能做到最好。


这些可能是难以测试的软件数百种症状中的一小部分。每一种症状看起来都是一个痛点,一个挑战,但归结起来,它们会产生数量级的差异。难以测试的软件是真正的噩梦!


为什么可测试性很重要


我们来看一下为什么这个问题很重要。


如果软件交付可以被分为 999 个开发部分和一个验证部分,那么这个问题就不那么重要了。即使在极端情况下,由于系统的不可测试性导致 1 个验证部分变成了 20 个,与软件的开发成本相比仍然不值一提。如果我们把精力放在优化 999 个开发部分上,会获得更好的效果,并且完全有理由忽略 1 个(或 20 个)验证部分,它们在整个问题中所占的比例并不大。


我想你们不会认为 999 与 1 这种比例分配是对的。虽然将软件交付分为“开发”和“验证”两个部分是很自然的,并且有一百万个影响变量,但我认为开发工作和验证工作之间的正确比例可能更接近 1 比 1。然而,具体的数值并不重要,重要的是你要知道验证是交付软件整体工作的重要组成部分。


因此,验证工作在复杂性方面存在数量级的差异,这是有问题的。如果开发和验证的比例是 50 比 50,而验证的成本高达 500,那么你就不可能交付软件了。


这么大规模难度的增加意味着所有有效交付软件的工作都可能被验证工作所抵消。考虑到验证的巨大负速度影响,所有用于提升团队效率的努力都可能是毫无意义的。如果我们不能控制软件的可测试性,所有的交付日期都将是猜测,评估技术变得完全无用。


如果你以任何方式参与了软件交付,这些应该会让你感到害怕。软件的可测试性可能是你的致命伤。这就是为什么软件的可测试性很重要。


如果我们不解决验证问题对软件交付的巨大影响,而只是把它藏起来或扔给一群不知情的“质量保证”人员,我们将无法有效、可预测地交付高质量的软件。只是擅长构建软件是不够的,我们必须擅长构建可测试的软件。


低效的质量保证


质量保证是软件测试的规程,一个深刻、广泛且不断发展的领域。它也极具挑战性,要求测试人员具备分析能力、批判性思维、解决问题的能力、创造力和强大的工程技能,再加上一整套软技能:沟通能力、影响力、解决冲突能力等。从这一点上讲,我非常敬佩这些质量保证专业人士。


不幸的是,按照传统做法,质量保证在验证现代软件方面存在不足。原因是什么?因为质量保证的问题范围定义是从要测试的软件开始的。就好像是说:对于这个软件,我们该如何最有效地测试它?我们如何建立这个软件一定会为客户带来价值的信心?对于质量保证来说,软件(无论是规范还是实现)都是问题的来源。


这种思想是短视的,它过于狭隘地关注被测试的软件。因为软件可测试性存在巨大的差异,所以有关软件质量的主要问题应该是如何最小化测试软件所面临的挑战。如果软件难以测试,那么质量保证策略——无论多么深思熟虑、富有洞察力或创造性——都不会有效。再多的质量保证也不能避免数量级的可测试性问题。


换句话说,质量保证可以指导我们如何高效、有效地测试软件,但是当软件无法测试时,质量保证就无能为力了。


一些软件质量保证专家可能会反驳,他们声称左移、持续测试和内建质量这些概念包含了相同的想法。然而,所有这些关注的都是质量,而不是可测试性。他们认为应该尽早进行测试,或者经常进行,或者持续进行,这样就可以尽早(甚至在规范中)发现缺陷或从一开始就避免出现缺陷。虽然它们都是值得称赞的策略,并且都是健康测试方法的一部分,但都无法构建出可测试的软件,无法帮助避免数量级的验证问题。


不幸的是,不仅质量保证对于验证现代软件来说是低效的,作为问题解决者的质量保证人员,尽管他们有良好的意图,但有时候却会使情况变得更糟。


质量保证专业人士都很聪明,他们喜欢解决难题。虽然解决简单的问题也很棒,但不会为你赢得同伴的赞赏,也不会让你产生“我完成了一些了不起的事情”的多巴胺刺激,但解决困难的问题会。如果你将一个难以验证或自动化的问题摆在质量保证人员面前,他们很可能会卷起袖子深入研究。因此,许多质量保证团队不会抵制难以测试的软件,而是会热情地接受它们。对他们来说,这代表着一个可以展示他们价值、证明他们勇气的机会。


这种倾向相当普遍,但却是导致无效软件交付的一种病理。尽管软件是不可测试的,但质量保证专业人员仍然努力验证软件,从而把根本问题隐藏了起来。因此,团队的其他成员也就无法看到或意识到软件本身存在的错误,这些错误是在构建时忽视了可测试性而引入的。


我在此并不是想要贬低那些在质量保证领域工作的人,这是一种极具挑战性和价值的角色。我想要指出的是,在现代软件开发中,质量保证是不够的。无论如何优化和简化,没有任何一种质量保证过程能够克服难以测试的软件的问题。


可测试的架构和质量工程


因为在可测试性方面存在巨大的挑战,所以构建可测试的软件就变得至关重要。我们必须随着软件的演化而改变软件的验证策略,并利用这些策略来指导软件架构、设计和实现。验证策略是软件的输入,而不是在软件完成时才去创建的东西,可测试性应该成为软件设计的第一阶需求,与其他需求一样重要。


实现可测试的软件是质量工程的一个关键环节。


质量工程是关于如何主动和有意识地构建可测试的系统。验证系统可能是一项复杂、具有挑战性、耗时的工作,任何避免、缓和或减少问题的努力对有效和高效的软件交付来说都是非常有价值的。质量工程需要将所有质量保证方面的专业知识和对“如何验证软件”的痴迷应用在“如何从一开始就构建出更容易验证的软件”这个问题上。


不幸的是,由于工程师这个词在招聘时具有很高的市场价值,因此在行业中过度使用这个词是很常见的,许多人在招聘质量工程师时也这么做。


根据我们的定义,质量工程并不是指在设计和构建好系统以后才开始应用测试和自动化专业知识。这不是“这是一个很酷的系统,我将用工程专业知识来测试和自动化它”,也不是教质量保证专业人员如何编码,称他们为质量工程师,以及他们所做的质量保证工作。所有这些都很好,但它们都不能用来定义质量工程,它们都无法解决可测性问题。真正的质量工程,应该是有意且明确地使用质量保证专业知识来构建可测试的系统。


你一定需要质量工程师这样的角色来实施质量工程吗?当然不是。质量工程是一种软件开发哲学和方法,与头衔完全无关。简单地说就是:验证是软件交付的一个重要组成部分,质量保证不仅复杂而且极具挑战性,你不仅需要专家来验证软件,还需要构建可验证的软件。这些专家可以是开发人员、质量保证工程师或任何你认为合适的人。关键在于他们掌握的技能,以及这些技能被应用在正确的问题上,至于头衔是什么并不重要。


结论


前面的内容并没有证明某些系统的测试难度比其他系统大几个数量级,确实没有数据、研究或实证证据。我无法为你提供这些东西,我也不会相信那些提供给我这些东西的人。


我能为你提供的是我 20 多年来帮助公司构建复杂软件系统的经验。我所看到的大部分情况不是公司在努力构建难以构建的系统,而是在努力验证那些构建后的系统。


在实现大型的项目时,最常见的项目问题是系统可以按照预期和目标到达某些点,但随后碰到了一堵无形的墙,然后缓慢地爬行,交付日期被一再推迟。沮丧的高管开始出现在团队会议上,重申每个人都已经知道的截止日期,并要求每天更新指标,如每日执行的测试用例。这种集体性的愤怒会降临到那些被认为对无法完成验证负有责任的人身上。为什么需要这么长时间?我们设计并构建了这个系统,为什么不能测试它?这能有多难!


透过这些团队,你无疑会发现一些难以测试的软件。你将看到人们与测试数据做斗争,无法设置测试场景和构建复杂的工具和过程来管理它们。你将看到团队努力构建确定性的自动化测试,为此投入了大量的资金,却被不稳定的环境所阻碍。你会发现,因为其他的手段不可行,所以团队不得不过度依赖 UI 驱动的 E2E 测试自动化,需要一整夜运行成千上万的测试套件,并在第二天花一整天来调试结果(当然是在重新运行失败的测试用例三次之后)。你会发现这些问题,可能还有更多。


很多人确实看到了这些问题,并得出了具有破坏性的错误结论——团队存在测试问题。


这种情况并不是糟糕的测试实践或不合格的测试人员造成的,而是假设所有系统都是可测试的灾难性假设造成的。这些项目现在处于一种不那么令人羡慕的状态,让系统完全运行起来需要付出的努力比让它稍微运行起来所需要的努力,要多得多。对于这类系统,验证软件将比构建它困难得多——而且可能根本不可能。


如果你希望测试是有效的,需要可测试的系统,这意味着你必须构建可测试的系统。你必须考虑软件的验证方法,并将其融入到软件的架构、设计和实现中。让那些在软件验证方面有经验和专业知识的人(不管他们的头衔是什么)参与到这些过程中。在你的组织中培养质量工程文化,寻求将验证问题最小化的方法,并在一开始就避免数量级的不可测试软件问题。


原文链接:

https://medium.com/slalom-build/on-untestable-software-6e64c34bfbad

作者介绍

质量工程师、软件开发人员、顾问、悲观主义者——现任 Slalom Build 质量工程高级总监。


活动推荐

如果你想了解更多关于测试、可观测的话题,欢迎关注将于 10 月 30 日 -11 月 1 日举办的 QCon 全球软件开发大会(2022)北京站。会议策划了【测试环境治理】【云原生时代的可观测最佳实践】相关专题,分别由京东技术总监何学奇、阿里云资深技术专家周小帆(承嗣)两位老师出品,目前已邀请来自阿里、腾讯、百度、去哪儿网、快手、云杉网络等公司的一线专家进行演讲分享,共同挖掘业界优秀的实践案例、探讨测试环境治理之路,带你了解典型行业在云原生技术场景下对于可观测技术的落地案例与最佳实践。本次 QCon 北京站将汇聚一线大咖分享 100+ 精彩演讲,不容错过,点击此处直达大会官网查看日程。团队购票可享更多优惠,购票请咨询:15600537884(微信同电话)



2022-09-20 16:466385

评论 2 条评论

发布
用户头像
接到一个任务时,我会考虑怎么测,同时靠谱的开发也会考虑怎么给我测
2023-03-02 18:19 · 浙江
回复
用户头像
好像说了什么,好像又什么也没有说
2022-09-20 17:58 · 福建
回复
没有更多了
发现更多内容

ArrayList 源码分析

读钓

Java 源码分析 jdk源码

ARTS week1

紫枫

ARTS 打卡计划

使用 webpack 搭建一个简单的 React 脚手架

张张张小烦

react.js

音视频会议系统-Janus的安装与布署

音视频专家-李超

音视频 WebRTC

【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

一枝花算不算浪漫

并发编程 jdk源码 线程池

从引用聊一聊 Java 垃圾回收

Rayjun

Java 引用 对象

ARTS打卡Week 01

teoking

android WebRTC

关于工作的一点总结

李印

工作思路

MySQL实战四十五讲基础篇总结(七)

一个有志气的DB

MySQL 性能

leetcode练级-两数之和

幸福三寸日光

算法 LeetCode js

Algorithm week 1: Merge Two Sorted Lists

猫吃小怪兽

算法 链表 ARTS 打卡计划

时间管理的本质

史方远

职场 心理 成长

MySQL实战四十五讲基础篇总结(四)

一个有志气的DB

MySQL 索引结构

k8s 上运行我们的 springboot 服务之——我们的springboot能够在k8s上运行

柠檬

k8s istio springboot

谈谈控制感(9):提升控制感排名第一的武器

史方远

职场 心理 成长

谈谈我的云笔记使用之路

读钓

学习 个人成长 写作

RabbitMQ-AMQP

云淡风轻

RabbitMQ

谈即时编译优化-以异常堆栈丢失为例

寻筝

数据与广告系列二:计算广告和推荐系统

黄崇远@数据虫巢

数据挖掘 大数据 互联网 广告 推荐系统

青春期的打油诗

李印

随笔

编程入门整理

紫枫

读书笔记

《陆蓉行为金融学讲义》 - 读后感

石云升

读书笔记 投资 行为金融学 理性 公平

鄙视链 & 全栈

伯薇

学习 能力提升 全栈

云直播平台的选型与使用

音视频专家-李超

MySQL实战四十五讲基础篇总结(六)

一个有志气的DB

MySQL 读写锁

Spring Security密码登录流程源码分析

读钓

源码分析 spring security springboot

宏在C++中的替代解决方案

老王同学

c++ 模板 template

MySQL实战四十五讲基础篇总结(五)

一个有志气的DB

MySQL 索引

其实,还是让我挺震惊的,程序员的换行率竟然高达 40%

非著名程序员

程序员 程序人生 自我思考

Tomcat学习分享

李印

tomcat

Java 数据持久化系列之JDBC

程序员历小冰

Java JDBC 持久化

只擅长构建软件是不够的,我们必须擅长构建可测试的软件 | QCon_软件工程_Blake Norrish_InfoQ精选文章