购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.1 测试左移

1.1.1 传统瀑布模型下软件测试的挑战

在早期传统的软件开发流程中,很多项目都是参考瀑布模型来进行开发的。瀑布模型的主要实践是将软件研发全生命周期中的各个阶段——需求分析、架构设计、实现设计、代码开发、单元测试、集成测试、系统测试、上线发布、生产运维,依次排列(如图1-1所示),按顺序执行,即大规模集中的测试工作在软件功能设计与开发完成后才开始。

图1-1 瀑布模型下的软件研发全生命周期

这种模型最大的问题在于,很多软件缺陷其实是在研发早期就引入的,但是发现缺陷的时机往往会大幅度延后。缺陷发现得越晚,定位和修复缺陷的成本就越高。在系统测试阶段发现缺陷后修复的成本,大约是在代码开发阶段发现该缺陷后修复成本的40倍,或大约是在单元测试阶段发现该缺陷后修复成本的数倍,Capers Jones关于效能与质量的全局分析(如图1-2所示)直观地表明了这个观点。

图1-2 Capers Jones关于效能与质量的全局分析

根据Capers Jones的统计,大约85%的缺陷是在代码开发阶段引入的,但是因为这个阶段缺乏测试活动,所以发现的缺陷数量几乎为零。而到了软件研发的中后期,由于测试活动的集中开展,缺陷才被大范围发现,但此时的修复成本已变得非常高。

比如,在代码开发阶段引入的缺陷,以及影响接口的缺陷,要等到集成测试阶段才有可能被发现,影响用户界面和用户体验的缺陷要等到系统测试阶段才能被发现,这时返工(rework)的闭环周期被拉得特别长,这样定位问题(缺陷)、修复问题、回归测试的成本就会很高。

让情况变得更糟糕的是,Capers Jones的统计还是基于比较乐观情况的分析,因为其假定软件在研发过程中总是被严格开展单元测试和集成测试,但实际情况是,很多团队寄希望于通过最后的系统测试来发现所有问题,单元测试和集成测试往往会被“偷工减料”,这进一步产生了缺陷发现滞后的问题。

1.1.2 测试左移的早期实践

为了解决缺陷发现滞后的问题,最早的测试左移(shift-left testing)概念被提了出来,此时的测试左移倡导各个测试阶段对应的测试活动应该尽早开展,测试工程师应该在开发提测前就介入,同时将测试活动前移至软件研发生命周期的早期阶段。具体来讲主要包含以下3方面的实践:

加强单元测试,并且对单元测试的覆盖率提出门禁要求,代码的实现问题尽可能在单元测试阶段都被发现;

在开展集成测试前,增大接口测试的占比,接口缺陷尽可能在接口测试阶段被发现;

将集成测试和系统测试的设计与分析工作前置,与实现设计、代码开发阶段并行开展。

测试左移的早期实践,可以提前发现部分缺陷,降低研发过程的不确定性和风险,具体效果如图1-3所示。

图1-3 测试左移早期实践的效果

随着实践的深入,我们发现,如果能有效控制代码开发阶段代码本身的质量,就能更好地实现质量内建。为此,我们在原有实践的基础上增加了以下3方面的实践:

在流程上增加需求解读与评审环节,避免存在需求理解的偏差和不完备性,争取一开始就把业务领域的问题理解透彻,避免后期返工;

在代码开发阶段引入静态代码检查机制,并且不断优化静态代码的扫描规则,将常见问题、代码“坏味道”、安全隐患和性能隐患逐步纳入扫描范围;

贯彻执行代码评审(code review)机制,同时确保避免代码评审的形式主义,并针对代码评审时发现的典型问题在开发团队内形成闭环学习机制。

以上3方面的实践,可以实现缺陷发现的进一步前置以及缺陷数量的降低。实施之后的效果如图1-4和图1-5所示。

如果你以为上面就是测试左移的全部,那你就把事情想简单了。其实这只是测试左移的“冰山一角”。

图1-4 代码开发阶段的质量内建效果(缺陷发现的进一步前置)

图1-5 代码开发阶段的质量内建效果(缺陷数量的降低)

1.1.3 当前软件测试工程化的困局与解法

可能你已经发现前述的测试左移是完全基于瀑布模型的,但是现在,敏捷开发和持续交付等研发模式被广泛采用,再加上软件架构的持续复杂化,前述的测试左移只能在局部范围内发挥作用,我们需要探索并实践适应新时代软件研发模式的测试左移。为此,我们有必要先系统地探讨一下当前软件测试工程化的困局,理解困局将有助于我们对测试进行优化。

总体来看,当前软件测试工程化的困局主要表现在以下3个维度:

技术实现上,软件架构的复杂度越来越高;

团队管理上,开发团队和测试团队的协作成本因为“筒仓效应”变得越来越高;

研发模式上,敏捷开发、持续交付、DevOps等的实践对测试活动提出了全新的要求。

接下来,我们依次展开讨论。

1. 技术实现维度上的困局与解法

从技术实现维度来看,软件架构的复杂度越来越高,软件本身的规模越来越大,传统的测试模式越来越“力有不逮”。

早期的软件基本采用单体架构,通过后期基于黑盒功能的系统测试基本能够保证软件质量。但是如今的软件架构普遍具有冰山模型(如图1-6所示)的特征,基于黑盒功能的系统测试往往只能对水面上的一少部分GUI(Graphical User Interface,图形用户界面)进行验证,大量的业务逻辑实现其实都在水面以下的微服务中,想通过水面上的GUI部分覆盖水面下的所有业务逻辑几乎是不可能完成的任务,因为你可能都不知道水面下有什么。试问,在传统黑盒测试模式下,又有多少测试工程师能够对被测软件的架构设计、调用链路、数据流状态等有清晰的理解呢?

图1-6 冰山模型

现在互联网产品的后端往往非常庞大和复杂,一般由几十到几千个微服务相互协作共同完成前端业务请求,这时候如果把测试寄希望于面向终端用户的系统测试,那么你能够发现的缺陷就会非常有限,而且发现缺陷之后,在调用链路中定位到出问题的微服务的成本也会很高。

在这种情况下,最优的测试策略就是先保证后端每个微服务的质量,这样集成场景下没有问题的概率就能大幅度提高。这就要求测试工作必须前置到微服务的接口开发层面,把大量的组合逻辑验证交由接口测试来覆盖,在GUI层只做基本的业务逻辑覆盖即可。

由上面的分析可以看出,软件架构后端的复杂化对测试的介入时机提出了新的要求,随着微服务架构的发展,测试重点必须从GUI端逐渐左移到API端,此时测试工程师的能力也必须随之扩展,其已经不能完全基于黑盒功能来设计测试用例,而必须知道更多架构和接口设计上的细节才能有效开展测试用例的设计,这些都要求测试介入的时机必须提前,即左移到架构设计。

2. 团队管理维度上的困局与解法

从团队管理维度来看,开发团队和测试团队的协作成本因为筒仓效应变得越来越高,继续采用独立测试团队和开发团队的做法越来越行不通,我们可以通过实际工作中常见的真实例子来感受一下这个困局。

在开发和测试采用独立团队的情况下,当测试工程师发现一个缺陷时,他要做的第一件事就是把缺陷的详细情况了解清楚并且完整记录在缺陷报告中。他需要找到最简单的可稳定重现缺陷的操作步骤,并且需要提供相关的测试数据,还需要对出现缺陷时的软件版本号、环境细节、配置细节等都做详尽的记录。更进一步地,为了便于开发工程师重现缺陷,他最好把出问题时的日志以及相关截图都保留好,一同记录至缺陷报告。这样一份高质量的缺陷报告往往需要花费测试工程师不少的时间。

但是当这份缺陷报告被提交后,如果缺陷不是来自生产环境,那么开发工程师往往并不会立马处理缺陷,因为开发工程师一般会选择确保能够连续完成当前负责的工作,尽量避免被打断。一般过了大半天或一两天,等开发工程师负责的工作告一段落后,他才会开始处理这个缺陷。此时他要做的第一件事就是重现缺陷,在重现缺陷之前,他必须按缺陷报告提供的详细信息重建测试环境,其中包括环境安装、测试数据构建等一系列步骤,所以往往也要花费不少时间。

如果问题能够重现,则可以进一步定位问题;如果问题不能重现,则可能这个缺陷在流程上就要被打回去加以复现。假定现在问题能够重现了,你会发现修复缺陷的过程往往是很快的,因为这个坑就是开发工程师自己挖的。

修复缺陷以后,开发工程师提交代码,集成流水线会生成对应的待测版本并通知之前的测试工程师进行验证。但是此时测试工程师大概率在处理其他测试工作,测试工程师同样不希望被打断,所以不会为了验证这个缺陷立马搭建环境。从效率角度出发,测试工程师一般会选择将多个缺陷集中在一个版本上一起验证,这样就能省掉很多环境搭建的时间。所以从缺陷修复完成到测试工程师验证这个缺陷,往往需要等好几天。

从上面的过程描述中我们可以发现,从缺陷被发现到缺陷最终被验证的整个过程中,真正有效的工作时间占比很小,大量的时间都被流程上的等待和环境安装等耗费掉了。整个过程中,开发工程师没有偷懒,测试工程师也没有偷懒,他们各自都选择了效率最高的方式开展工作,但是从全局视角来看,效率仍十分低下。据一些企业内部的不完全统计,每个缺陷全生命周期中一般会有超过80%的时间被跨团队的过程流转浪费掉。

除了上述时间上的浪费,还有以下原因使得测试团队与开发团队各自独立的组织设置越来越寸步难行。

(1)独立的测试团队往往在开发后期才介入,很难有效保证测试的覆盖率和质量。

(2)开发测试比持续增长,测试人力投入越来越大,实际收益却很低,测试团队进行的测试活动并不能显著降低现网问题数。

(3)需求本身会不断变化,需求的实现也会随之变化,开发团队和测试团队之间的需求传递效率往往十分低下,这在增加漏测隐患的同时,也增加了交接成本。

(4)如果开发团队要快速迭代软件版本,这就要求测试团队具有很高的效率和很短的反馈周期,独立的测试团队很难跟上快速迭代的版本需要。

(5)独立的测试团队有点像“保姆”,这直接导致开发团队的自测意识不够,心理上依赖测试团队,使得质量内建形同虚设。

(6)由于开发团队不负责测试活动,可能未积极考虑如何降低测试的难度,可测试性设计甚至不会被纳入开发工程师的考虑范围。

所以,试想一下,如果测试工程师和开发工程师是同一批人,过程流转造成的大量时间浪费是不是就不会发生?测试活动是不是就能提前介入?需求变化的传递是不是就会更加顺畅?测试的反馈周期是不是也会进一步缩短?开发团队的质量意识是不是也会增强?可测试性问题是不是自然会被纳入开发工程师的考虑范围?这也就是现在先进的软件组织广泛推崇开发者自测的原因,而开发者自测可以说是测试左移的一种有效落地途径,能够最大程度满足质量内建的各种要求。

3. 研发模式维度上的困局与解法

从研发模式维度来看,敏捷开发、持续交付和DevOps等研发模式愈发流行,产品的研发节奏越来越快,传统的“开发提测之后进行测试,然后上线发布”的测试模式面临很大的挑战。在当前新的研发模式下,研发生命周期中的各个阶段(比如设计、开发和测试阶段)都被弱化,或者说边界变得非常模糊,一个迭代通常就包含设计、开发、测试和发布的全流程,已经很难有大把的时间专门用来集中开展测试活动,工程师的能力边界正在变得模糊,普遍需要全栈工程师。

在这种背景下,必须把测试实践全程融入研发的各个阶段,把控各个阶段的质量,而不能依赖于最后的系统测试。我们需要转变观念,传统研发模式下的系统测试以发现问题为主要目标,而现在的系统测试应该以“成果展示”和“获取信心”为主要目标。

1.1.4 测试左移的进阶实践

为了系统性解决软件测试工程化的困局,我们需要重新审视测试左移的原则与实践,在原有测试左移实践的基础上加入新的原则和实践。新时代的测试左移给整个软件测试体系带来了理念上的转变,软件测试不仅仅是在研发过程中发现缺陷,更要致力于在研发全过程中有效推行质量内建,把软件测试活动升级为软件质量工程。为此,我们需要引入以下测试左移的原则和实践。

1.软件质量全员负责制

软件质量全员负责制也可以称为“利益绑定”,这是很关键的一条原则,属于底层逻辑的范畴。在体制设计上,必须让整个研发团队共同对软件产品质量负责,毕竟软件质量不是测出来的,而是开发出来的。如果软件质量出现问题,应由整个研发团队共同负责,而不是让测试团队“背锅”,这种认知上的进步与变革是测试左移能够顺利推行的基本前提。

我们知道,保姆型团队对组织成长是有害的,只有支持型团队才能够发挥更大的价值,从而推动组织更好地成长。当软件质量由整个研发团队共同负责的时候,测试团队就能完成从保姆型团队向支持型团队的蜕变。

2.把测试前置到需求分析和方案设计阶段

在测试左移的早期实践中,测试活动已经被前置到开发阶段,我们可以进一步把测试前置到需求分析和方案设计阶段。这样,测试人员除了能够深入理解需求,在前期掌握详实的需求信息,更重要的是还能够及时评估需求本身的质量,比如分析需求的合理性及完整性等。这样后续的测试分析与测试设计才能有的放矢地开展,实现测试用例先行,争取一次性把事情做正确,避免产生“信息孤岛”以及由此产生的各种潜在返工。

这里推荐使用行为驱动开发(Behavior Driven Development,BDD)和特性驱动开发(FeatureDriven Development,FDD)的方法,在进行需求评估时更多地从测试视角去思考问题,按照编写用户故事或用户场景的方式,从功能使用者的视角描述并编写测试用例,从而让产品经理、开发人员和测试人员着眼于代码所要实现的业务行为,并以此为依据通过测试用例进行验证。

当然,以上实践对测试人员也提出了更高的要求,他们必须掌握行为驱动开发、特性驱动开发、领域驱动设计(Domain Driven Design,DDD)以及实例化需求(Specification By Example,SBE)等技能。

3. 鼓励开发人员自测

一方面开发人员必须对软件质量负责,另一方面测试活动正在不断渗透到开发的各个阶段,同时对测试人员的技能要求越来越向开发人员看齐,由开发人员自己来承担测试工作的诉求正变得越来越强烈。我们需要的不再是独立的开发人才和测试人才,而是全栈型人才。在这种大背景下,开发者自测就变得理所应当了。我们要将传统职能型团队重组为全栈团队,不然质量左移、质量内建只会流于表面。

但是说到让开发人员自己完成测试工作,我们常常会听到很多质疑声,质疑的焦点是开发人员是否适合做测试,这里我们展开讨论一下。

从人性的角度来看,开发人员通常具备“创造性思维”,自己开发的代码就像亲儿子一样,怎么看都觉得很棒;而测试人员则具备“破坏性思维”,测试人员的职责就是尽可能多地找到潜在的缺陷,而且专职的测试人员通常已经在以往的测试实践中积累了大量典型的容易找到缺陷的模式,所以测试人员与开发人员相比,往往更能客观且全面地做好测试。

从技术层面来看,由开发人员自己完成测试,会存在严重的“思维惯性”——通常开发人员在设计和开发过程中没有考虑到的分支和处理逻辑,在自己做测试的时候同样不会考虑到。比如对于一个函数,它有一个 string 类型的输入参数,如果开发人员在做功能实现的时候完全没有考虑到 string类型的参数存在 null 值的可能性,那么代码的实现里面也不会对 null 值做处理,在测试的时候就更不会设计针对null 值的测试数据,这样的“一条龙”缺失就会在代码中留下隐患。

上述分析非常客观,笔者曾经也非常认同,但是在经历并主导了国内外多家大型软件企业的开发者自测转型实践之后,笔者改变了看法。开发人员其实是最了解自己代码的人,所以他们能够最高效地对自己的代码进行测试,开发人员可以基于代码变更自行判断可能受影响的范围,实现高效的精准测试。同时,当开发人员有了质量责任和测试义务之后,测试能力就会成为其技能发展的重要方向。我们说“好马是跑出来的,好钢是炼出来的”,只有通过实战,开发人员的测试分析与设计能力才能提升,进而开发的内建质量才能提升。可以说,开发者自测是软件质量提升的必经之路。

4. 在代码开发阶段借助TDD的思想

这里的TDD是指测试驱动开发(Test Driven Development),但我们并不是要照搬TDD的实践,而是借助TDD的思想,用测试先行的思路,帮助开发人员梳理和理解需求,完成更好的代码设计与实现,缩短代码质量的反馈周期,提高软件质量。

5. 预留测试时间

在做项目计划的时候,尤其在让开发人员进行时间评估时,必须为自测预留时间。一个功能可以交付的标准不仅仅是功能的实现,对应的测试也是需要时间成本的,这需要管理层进行思维转换,否则测试左移只能停留在概念层面,很难真正落地。

6.提高软件的可测试性

提高软件的可测试性是测试左移中一个重要的实践,因为它可以有效地帮助我们设计适合团队的测试策略。我们需要测试人员在参与需求分析和方案设计的过程中,能够提出相关的可测试性需求,帮助研发人员设计出易于测试的软件架构和代码模块,从而提高测试工作的效率和有效性。关于软件的可测试性设计,详见1.3节。

1.1.5 测试左移的深度思考

下面分享笔者的一个感悟:很多时候,我们低估了测试左移的价值。它不仅仅是一种研发模式的改进,更反映了当今软件研发的一个底层逻辑——今天的软件组织,正在由流程驱动转变成事件驱动。

流程驱动是什么呢?是设置一系列的条条框框,从而把研发活动固定为整齐划一的一系列步骤。而高效的软件质量实践能够靠这个来完成吗?答案显然是否定的。

现在的研发活动本质上是靠一个个的事件驱动的,让研发团队以完成事件为指引,充分发挥主动性,充分给予工程师自由。从全局的视角来看,测试左移能被行业所接受,其实反映了软件组织的管理正在向事件驱动的模式转变。

1.1.6 总结

本节探讨了传统瀑布模型和敏捷开发、持续交付、DevOps等研发模式下的测试左移实践,并介绍了开发者自测的实践。 rIf/EkD8KpdrVI4LGgzehk2YIOp9BMFPuTyMmgz4U+a1wZGcHCNnpOEL+PV/eOuv

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开