认识软件测试中的黑天鹅

发表于:2014-11-10来源:uml.org.cn作者:不详点击数: 标签:软件测试
软件测试中的“黑天鹅”

1、软件测试中的“黑天鹅”

几年前,我带领的一个测试小组遗漏了一个严重的bug到网上,当用户反馈这个bug后,我们对它进行了深入的分析和重现,最终所有人一致认为,这个bug能够发生实在是机缘巧合,因为它需要多个条件同时发生才有可能触发,比如“XX算法开关必须打开、XX算法开关又必须关闭、XX参数必须取某个特定值、用户的使用环境必须是XX个场景、硬件必须是使用XX接口板、软件必须是XX版本、XX的带宽恰巧又不够。。。”,在用户那里,这些条件有一条不满足,就不会发生这个bug。

由于这个bug的影响比较严重,又是用户报告的,照例要提交缺陷根因分析报告。其中,报告里有一项“后续如何发现这类缺陷,确保不遗漏?”我们不知道如何避免这样的缺陷再次发生,实际上,如果从正向设计的角度,我们无论如何也不会设计一个满足上述所有条件的用例,也不会去执行这样的测试。不过,我们不得不费尽心思地去思考,这个bug的发生是如何有其合理性的、是可以解释的、因此也是可以预防的,以填补报告里这一项的空白。

前不久我阅读了一本书叫做《黑天鹅》。数千年来,人们认为世界上只有白天鹅,但是发现第一只澳大利亚黑天鹅以后,这种牢不可破的观念被颠覆了。作者指出“黑天鹅事件”满足三个特征:

- 稀有性,或意外性,即它通常在预期之外;

- 冲击性,即它会产生极端影响;

- 事后(而不是事前)可预测性,人的本性促使我们在事后为它的发生编织理由,并且或多或少地认为它是可解释和可预测的。

很多重要的线上bug不就是测试中的“黑天鹅”吗?它们的发生在你的预期之外、产生了极端的影响、事后人们总是认为这些bug是可以避免的。

仔细想一想,你所在的项目中是否有“黑天鹅”?2013年1月 31日,亚马逊主页瘫痪近1小时,你是否提前预期到了(《黑天鹅》书中写到,“从对称的角度讲,一个高度不可能事情的发生,与一个高度可能事件的不发生是一样的”)?12306网站的瘫痪、高铁事故、ATM取款系统故障。。。,你是否能提前预期到?其实,你可能已经发现,“黑天鹅”离我们并不遥远,总是有那么多被认为不应该发生的现象实际发生了。

造成“黑天鹅”现象的可能有很多种原因,从测试的角度讲,我们如何来认识软件测试中的“黑天鹅” – 那些总是让人意想不到又产生严重后果的bug呢?

2、对“黑天鹅”视而不见

人们习 惯于对“黑天鹅”视而不见。“社会学家一直错误地以为他们的理论能够衡量不确定的事物”。人们相信通过使用趋势分析工具和复杂的数学公式可以帮助我们很好地预测股票市场的风险、挑选一支好的股票,结果大失所望的人不在少数;人们相信复杂的科学仪器和先进的数学、物理学、天文学等理论可以帮助我们很好地预测地震、海啸、天气,结果经常有令人意想不到的自然灾害发生。

人们习惯于使用确定性的理论去推测那些不确定的事物,在这个处处都充满了“变数”的时代,“以不变应万变”的做法已不合适。RBT(基于风险的测试)是个不错的方法,有些人会按照既定的套路使用RBT:在项目的某个时间点,邀请相关人一起收集风险、分析风险,然后按照风险分析结果开展测试活动。但是风险是时刻变化的、风险是不能确定的,你不可能提前预期所有的风险,你得学会“以动制动”,动态地、持续地应用RBT技术。哪怕如此,你仍然无法避免所有的风险,仍然会有“黑天鹅”发生,因为软件测试的过程充满了随机性。

ReqBT(基于需求的测试)是个至少从数学上说很严谨的测试技术,它会用因果图的方法把业务中的每一个原子逻辑规则都表达出来。ReqBT的创始人Richard Bender认为只要使用了ReqBT方法,不会遗漏一个从黑盒角度讲、功能上的、业务逻辑相关的、严重的bug。我深知这套方法的妙处,但仍然对如上的陈述有所怀疑,原因无他,测试中的不确定性太多,没有哪个确定性的理论是银弹,可以保证没有某类的“黑天鹅”发生。

但这并不表示这些形式或模式确定类的技术没有任何用处、只要使用不确定性的技术比如ET(探索性测试)、RST(快速软件测试)等就好了。应该说,这些确定性的方法(RBT、ReqBT等)和不确定性的方法(ET、 RST等)结合起来使用的话,“动静结合”,会更有效地减少测试中“黑天鹅”的发生。

3、判断是否是“黑天鹅”

想知道哪些重要的软件缺陷是“黑天鹅”,哪些不是?只要问一问,这些bug与出现之前所预期的相比较,是否是在预料之中的?

我们会发现,这些重要的bug大体可以分为两类:一类是可以预期发生的,但是并没有采取及时的措施去规避;一类是不被预期发生的,认为不应该发生这种缺陷。这就启发我们在做RCA(缺陷根因分析)时,对不同类bug的分析会带来不同的收获。

分析可以预期发生的严重缺陷,可以很好地帮助我们改进已经在实施的确定类方法或技术中的不足。比如在实施RBT过程中,为什么没有识别出某个风险?或者为什么对风险级别的估计不足?或者为什么没有对已知的风险采取风险规避措施?再比如在应用MBT(基于模型的测试技术)过程中,为什么某一个因子在建模时被遗漏了?或者为什么基于模型生成测试条件或测试用例时某个重要的参数被忽略掉了?再比如按照企业既定的流程管理软件测试的过程中,为什么在测试策略或测试方案中忽略了这一风险?或者为什么在测试分析和测试设计中没有覆盖这个风险点?或者为什么在测试执行环节没有发现这个缺陷?等等。通过这样对的RCA过程,可以更好地帮助我们改善既有的测试方法或流程。

分析不被预期发生的严重缺陷,可以为我们开展不确定类测试方法或技术提供更多的输入和想法。例如本文开篇所举的那个关于某算法失效的例子,想通过确定类的测试方法、正向的方式提前设计(也就是预期)相关的测试用例,基本不大可能。相反,我们换一个角度,也许在ET中探索出这个缺陷的几率更大一些,比如适当增加与用户应用环境相关的各种因子组合的复杂场景测试的 session。

《黑天鹅》是一本关于不确定性的书。“有两种认识现象的方式。第一种排除不正常的现象,只关注正常现象。研究者不理会意外事件,只研究正常案例。第二种方法则认为,为了理解这一现象,人们需要首先考虑极端现象,尤其是当它们有非同寻常的效应累积的时候,比如黑天鹅现象。”对于测试而言,如果希望了解我们的不足,这两类缺陷(可以被预期的缺陷和不可以被预期的缺陷)都要分析。

4、我们所不知道的更有意义

“黑天鹅的逻辑是,你不知道的事比你知道的事更有意义,因为许多黑天鹅事件正是由于它们不被预期而发生和加剧的。”作者认为现在的世界由不确定性主导,不论这个结论正确与否,单就测试而言,这个结论是非常适用的,软件测试是由不确定性主导的。测试就是寻找未知的过程,这些未知的事情包括未知的缺陷、未知的被测对象的质量状况、未知的真实的被测对象是什么样的以及所有那些你认为你已经知道的但实际上是你不知道的软件相关的信息,这些未知给予测试挑战的同时也赋予测试很大的意义。

可以这样说,所有的测试用例都是基于测试人员已知的知识设计出来的,执行这些用例有机会发现那些能够被预期的缺陷;而测试中的“黑天鹅”- 那些重要的不被预期的缺陷,有多少是由测试用例发现的?

分析每一只“黑天鹅”,会帮助我们发现那些原本对于我们是“未知”的东西,每一只“黑天鹅”的遗漏,都是由于一些“未知”的因素,或者是未知的知识或信息、或者是不知道某些信息的重要程度而加以重视。下一次测试中,就会有意识地尽量拓宽我们的已知领域,减少“黑天鹅”发生的次数。

探索性测试在拓宽“已知领域”方面功不可没。当我们需要探索时、当我们不知道下一步测试什么时、当我们想要获得更多的想法时,我们使用探索性测试,ET有助于拓宽我们的测试深度和测试宽度,尽可能扩大已知领域的边界,缩小我们未知的领域。

5、结论

你看到一个后果很严重的缺陷,正是由于你认为它不应该发生?这是不是很奇怪?不管你采用什么样的测试方法、你的测试团队有多么强大、你在测试上投入了多少人力物力,测试中的“黑天鹅”总会发生。

作者在《黑天鹅》书中指出人性的一个弱点:“习惯于学习精确的东西,而不是总体的东西”,并且把“由于只关注那些纯粹而有明确定义的‘形式’而导致的错误”称为‘柏拉图化’”。测试的柏拉图化思想会让你只关注外在的形式,例如测试的流程是否遵守、测试的模板是否应用、测试方法的具体步骤是否实施,而开始忽略其他不那们具体不那么明确不那么美好和解释得清的事务,忽略那些显得有些混乱和不可琢磨的事务,比如在有些人眼里,探索性测试不容易管理、不好解释给别人、步骤不那么明确、有太多不可琢磨的东西在里面。

就像敏捷里承认变化总会存在而去拥抱变化一样,我们也应该正视测试中“黑天鹅”现象的存在,并尽最大可能地多尝试以减少“黑天鹅”发生的机会(拓展已知领域、减少未知领域),并且一旦“黑天鹅”发生了尽最大可能地把握黑天鹅机会(分析那些未知点,更好地应用不确定类的方法,寻求测试改进)。

1. 历史与三重迷雾

在“认识软件测试中黑天鹅”一文中,我描述了什么是软件测试中的黑天鹅及其特点,本文将探讨测试中的黑天鹅发生之前、之后、以及正在发生之中的故事。 《黑天鹅》一书的作者Nassim指出“历史是模糊的。你看到了结果,但看不到导致历史事件发生的幕后原因。”其实,测试何尝不是这样,假如把测试看成一个盒子,这个盒子也是模糊的,你看不到盒子里面是什么,整个机制是如何运行的。 书中描述:“对待历史问题,人类思维会犯三个毛病,我称之为三重迷雾。他们是:

--假想的理解,也就是人们都以为自己知道在一个超出他们认知的更为复杂(或更具随机性)的世界中正在发生什么。

--反省的偏差,也就是我们只能在事后评价事务,就像只能从后视镜里看东西(历史在历史书中比在经验现实中显得更加清晰和有调理)。

--高估事实性信息的价值,同时权威和饱学之士本身有缺陷,尤其是在他们进行分类的时候,也就是进行‘柏拉图化’的时候。” 我很容易联想到,这三重迷雾分别对应测试中的黑天鹅发生之前、之后、以及黑天鹅形成之中的故事,即:发生之前的“盲目预测”、发生之后的“假想解释”、以及黑天鹅形成之中的“柏拉图化”。

2. 黑天鹅发生之前的“盲目预测”

“第一重迷雾就是我们以为我们生活的这个世界比它实际上更加可理解、可解释、可预测”。打开收音机或电视机,你就会听到或看到,每天都有无数的人在信心满满地预测着各种各样的事情:股市的走势、房价的走势、战争是否会爆发、疾病是否会流行……

正向Nassim指出的那样,“几乎所有关心事态发展的人似乎都确信自己明白正在发生什么。每一天都发生着完全出乎他们预料的事情,但他们就是认识不到自己没有预测到这些事。很多发生过的事情本来应该被认为是完全疯狂的,但在事情发生之后,看上去就没有那么疯狂。这种事后合理性在表面上降低了事件的稀有性,并使事件看上去具有可理解性。”就拿我所生活的这座城市-上海来说,近来的许多事件都在证实着这点:黄浦江上打捞出数千头病死猪、H7N9的流行,昨天突然看到一条“上海自来水中添加了XX”的微博更是令人触目惊心,我们内心都预测这些事情不应该发生,可是实际上却发生了。

这种黑天鹅发生之前的“盲目预测”让我想到软件测试中版本发布之前的“测试评估(test evaluation)”。一个产品经过测试团队的集中测试后,发布到用户那里,谁能准确预测是否会出现“黑天鹅”呢?在你的团队,在版本对外发布之前,是否需要测试团队填写一个关于产品质量的测试评估表?下图是测试评估表的一个样例。

这里的特性指的是“测试特性(test feature)”。根据各团队上下文的不同,这个测试特性可能与开发特性直接对应,也可能不与开发特性一一对应,每个测试特性对应一条到多条需求(用户需求或系统设计需求)。我最常看见的质量评估说明是“XX特性基本功能正常。”有些人会在后面附上所发现的比较严重的bug。这样的描述显然并不令人满意。

问题是:

每个特性质量的A/B/C/D的结论是否准确?所有特性的A/B /C/D的结论加一起又如何判别整个系统的质量?是否所有特性的质量都是A或B,版本就可以对外发布,并且发布以后不会出现令人意想不到的“黑天鹅”现象?测试人员给出的A/B/C/D有多大成分是基于一种feeling给出的?

对于任何一条需求,开发人员的任务就是实现它,无论是由一个项目组实现,还是多个项目组配合实现。但是,对于测试人员,考虑的事情就复杂一些。我们除了要验证这条需求本身的功能实现是否正确,还要验证该需求和其它功能之间的交互,还要考虑客户可能使用的各种场景(Scenario),包括各种组网场景、各种参数的配置等。如果把测试场景和测试特性交互起来,测试就无穷无尽了,并且也没有必要在每一种测试场景下,都验证被测特性的基本功能、异常功能、与其它功能的交互、非功能属性等各方面。如何设计更有效的、有限数目的用例尽量做到最大的测试覆盖,这属于另外一个话题,本文不做探讨。

毫无疑问,尽管测试设计人员和测试执行人员精疲力尽的试图覆盖该特性的所有可能的场景,测试人员仍然只覆盖了一小部分的场景下的该特性的部分测试用例,如果没有Fatal的遗留问题,是否该特性的质量就可以评价为A或 B,并且认为可以对外发布了呢?这种测试评估工作,与其说是让测试人员对被测对象的质量进行测试评价,不如说是让测试人员对被测对象进行质量预测,因为测试人员所了解的只是部分信息,而不是全部。Nassim说“在这些错误的预测和盲目的希望中,有一些愿望的成分,但也有知识的问题。”我更愿意相信,测试人员对产品质量的预测主要是“知识的问题”,毕竟完整测试是不可能的。 既然测试无法做到完整覆盖,不如在有限的时间和资源内,尽可能覆盖典型的场景,测试评估中针对已经覆盖到的典型场景进行评估,具体地,James Bach和Michael Bolton在RST(Rapid Software Testing)课程里提出的三步法可以供参考:

测试中发现了哪些问题;

做了哪些测试活动发现了这些问题;

测试活动本身的有效性如何,哪些地方可以改善。

那么对于典型场景之外的其他场景又如何评估呢,这与测试评价之前的那些测试活动息息相关,也就是与黑天鹅正在形成之中的那些故事有关,我们在后面第四部分再探讨。

3. 黑天鹅发生之后的“假想解释”

不管怎样,那些你认为不应该发生的“黑天鹅”经常如期而至。很多组织要求缺陷发生后开展缺陷根因分析(RCA)(我总是在尽力避免使用“缺陷回溯”这个词)。尤其对黑天鹅这样的重要的bug,更要仔细开展缺陷根因分析了。对黑天鹅开展RCA的目的是利用这个黑天鹅,挖掘它带给我们的信息,从而尽可能在以后的测试中发现更多类似的缺陷,对开发而言则是在以后尽可能避免引入此类的bug。

RCA的目的是做到基于缺陷的过程改进,而不是解释黑天鹅的发生这个动作本身。不要试图去解释所有的黑天鹅,尤其是那些在实验室很难重现的黑天鹅。我看到有些测试团队,当黑天鹅发生了很紧张,又是一个严重的bug漏测了,赶紧组织人力调查分析,尽快写出一个看起来像样的缺陷分析报告。而阅读这个RCA报告,很难从中找出真正有力的措施可以有效避免今后这类bug不再漏测。举几个现实中的“RCA现象”:RCA报告中,错误的原因有“人为错误”、“沟通不畅”、“缺乏相应的测试用例”等;避免漏测的措施有“加强代码走读”、“加强白盒测试”、“提高测试设计能力”、“加强与开发人员的沟通”等。Nassim发现,“我们的头脑是非常了不起的解释机器,能够从几乎所有事物中分析出道理,能够对各种各样的现象罗列出各种解释,并且通常不能接受某件事是不可预测的想法。”

我曾对多个团队进行T-RCA(我提出的一个缺陷根因分析的方法)引导,发现一个有趣的现象,尽管各个团队所测试的产品是不同的,但是他们RCA分析的结论却是惊人的相似,很多团队都存在我上述所列的“RCA现象”。我分析原因大致有二,一是没有找到有效开展RCA的方法,没有找到缺陷发生的根本原因;二是很多团队使用了比较“细致”的RCA模板,模板里对每一项可能的情况进行了细致的分类,比如该缺陷所处的测试级别、涉及的测试活动、所属的测试类型、可能的原因分类等等。缺陷根因分析工作仿佛变成了测试人员只要对照模板逐一打钩去筛选就可以了,但实际上RCA是个高度探索性的过程,需要与缺陷相关的各干系人去沟通,需要从纷繁复杂的种种因素中创造性地找到改进的措施。面对着电脑,填写那些RCA模板中的空白项不是最主要的工作。实际上,某种程度上讲,过细的RCA模板简化了缺陷分析过程,掩盖了缺陷根因分析过程的复杂性,也比较容易导致分析结果的雷同。《黑天鹅》里的这句话也许可以给我们更多启示:“我们对周围世界的任何简化都可能产生爆炸性后果,因为它不考虑不确定性的来源,它使我们错误地理解世界的构成。”

4. 黑天鹅形成之中的“柏拉图化”

既然,测试中的黑天鹅的发生是个经常性的事件,那么测试的过程不也正处于黑天鹅形成的过程吗?想象一下,当前我们正在测试一个产品,我们了解黑天鹅理论,我们知道这个产品发布给用户后极有可能会冒出“黑天鹅”来,那么我们当前可以采取什么措施呢?

我在“认识软件测试中黑天鹅”一文中解释了什么是“测试的柏拉图化”:只注重外在的形式、尤其是针对具体明确的事情进行简单分类的时候,犯“柏拉图化”错误的人容易高估他们已经掌握的事实性信息的价值,而对大量的他们还不知晓的并且非常重要的信息视而不见。我是在2006年发现这个“我所不知道的大量信息的”,也同时发现了之前我所犯的“测试的柏拉图化”的错误,即特别重视诸如“测试用例设计个数、测试用例执行个数、发现的bug数”等这些数据,也许在这些明确的、外在的形式之外,有更多值得我们思考的东西。

那一年,我负责一个特性(大体就是在手机上通过蜂窝小区广播的形式收看电视)的测试,这个特性是个全新开发的特性,我是第一个、也是当时唯一一个测试人员,测试任务很紧张,因为这个特性已经定于一、两个月以后在香港某个运营商网络里首次商用。像大多数测试人员一样,我很辛勤地、按照既定方法和策略测试这个特性,在有限的测试时间里:我熟悉了这个特性、设计了很多测试用例、执行了大量的测试用例、发现了很多bug、对这个特性相关的每一个bug都了如指掌、通过和开发人员不断地交涉和定位bug还对该特性当前存在的缺陷非常清楚、熟悉这个特性的代码和问题定位手段。当我奔赴香港作为测试人员辅助开通这个特性的商用之前,研发团队解决了所有严重的缺陷,我很有信心应对各种可能的状况,我几乎认为我对这个特性的了解已经很完整了。

可是,当我抵达用户那里,看到我们的产品在真实网络中的运行环境、配置、使用方式,与用户和当地技术支持人员的各种交流,坐在地铁里看到身边的人员拿着手机正在开启使用我刚刚参与开通商用的特性,从真实网络环境中获取的大量错误报告和跟踪消息和后台日志。。。短短几天内,有关这个特性的、以前我所不知道的、大量的信息冲进了我的大脑,我像突然捡到宝藏一样地忙着分析日志、记录bug,生怕遗漏了哪个bug没有记录,然后回到实验室都不知道如何触发。

我发现了一个重要的事实:这一回,我只用了几天时间,没有设计任何测试用例,没有执行任何测试用例,却在一个我几乎认为没有什么严重缺陷的特性上,“不费吹灰之力”似的又发现了很多严重的缺陷。而且这些缺陷不是实验室触发的,而是就发生在用户的身上,有些遭到用户的投诉,有些用户还不知晓,换句话说,这些缺陷都是优先级很高的非常重要的缺陷。

从香港回来,我一直在思考,测试团队如何发现这些重要的缺陷?甚至如何让平常我们在实验室中的测试也能这么高效、这么有效?您可能已经猜到了,这就是后来备受重视的UBT(Usage Based Testing),或者有的行业叫做TiP(Test in Production),也有人称为Testing-in-the-wild。

再回到前面曾提到的一个关于测试评估的问题上:假如平常的测试更关注典型场景的测试,那么对于非典型场景如何测试以及如何评估呢?我想UBT是个不错的选择。既然无法做到全覆盖的测试,就不去做,不要试图在现有的测试模式下,测试设计和测试执行都投入很大精力去覆盖各种场景和交互,因为这样做收效甚微,依然达不到目的。平常的功能测试只做最普通、最典型、最重要场景下的功能验证,保证每个测试特性的基本功能OK。此外,还要开展UBT或TiP,主要考虑各种配置和场景,用模拟器模拟真实商用组网环境和业务模型,或者直接在用户使用产品的真实环境中开展测试。

假如被测系统如下图的方框,灰色的小块就是我们平常的功能测试覆盖,可能很多测试团队的做法是试图尽最大能力增加这些灰色小块的覆盖,但依然会有很多覆盖不到的地方。而UBT就相当于红色的范围,虽然没有针对性的设计测试用例,但由于模拟了可能使用的商用场景和业务,业务之间交互的测试在这种测试环境下自动进行,潜在的一般的bug都被自动发现(比较难触发的异常 bug依然发现不了),如果这样的UBT测试连续执行几天或数周都没有问题,此时测试的评估中就可以很有信心地写到:在XXX UBT环境下连续运行XX天,没有发现严重问题。 这样,也大大减少了版本发布后“黑天鹅”出现的几率。

5. 结论

如果你认同“测试的黑天鹅”就在我们身边,那么:

在“测试的黑天鹅”发生之前,不要在信息不完整的情况下对全局做“盲目预测”,因为你只掌握了部分信息,你要做的是更准确地陈述这些“部分信息”;

在“测试的黑天鹅”发生之后,不要试图对所有黑天鹅都做“假想解释”,更重要的是从已经发生的黑天鹅身上挖掘更有价值的信息,以减少更多类似黑天鹅事件的发生;

在“测试的黑天鹅”形成过程之中,不要犯“测试的柏拉图化”错误,重视你知道的信息,更重视那些你所不知道的大量的更有价值的信息。

原文转自:http://www.uml.org.cn/Test/201307162.asp