从这一讲开始,我们就进入了第 4 部分的学习:测试左移更体现敏捷测试的价值。因为敏捷更提倡团队对质量负责、预防缺陷胜于发现缺陷,这两点就意味着我们要构建出高质量的产品,把质量构建推向源头——需求,把测试活动左移到需求阶段,持续地对需求和设计进行评审、及时发现需求和设计的问题。
测试左移的目的是及时发现研发前期的错误,避免将错误带到代码阶段、测试阶段,TDD/ATDD(测试驱动开发/验收测试驱动开发)是更为彻底的测试左移,一次把事情做对,即零缺陷质量管理思想在软件研发中的实践,从而帮助企业节省研发成本并缩短开发时间。测试左移还包括测试计划和设计尽早开始,所有这些就构成了第 4 部分要讨论的内容。
测试左移还包括让开发人员做更多的测试、加强单元测试、持续集成等,在敏捷开发里这些都是核心实践,持续集成已经在第 12 讲讲过了,单元测试的内容将在第 42 讲中介绍。
在第 7 讲介绍敏捷团队中的专职测试人员时,提到过测试人员的责任之一是对软件的可测试性进行把关,可测试性是测试的基础或前提,没有可测试性,谈何测试?这一讲,我就来讲解什么是可测试性、可测试性的不同层次,以及如何提高用户故事的可测试性。
可测试性概念
软件的可测试性,从字面上的意思来说是指一个系统能不能进行测试。从理论上讲,可测试性基本上是由可观察性和可控制性构成的,还可以包括可预见性。
可观察性(Observability)是指在有限的时间内使用输出描述系统当前状态的能力。系统具有可观察性意味着一定要有输出,没有输出就不能了解系统当前处于什么状态,也就不能确定系统行为表现是否正确。
可控制性(Controllability)是指在特定的合理操作情况下,整个配置空间操作或改变系统的能力,包括状态控制和输出控制。系统具有可控制性表明一定要有输入,只有通过输入才能控制系统。
理解了前面两个,那可预见性(Predictability)也就好理解了,就是在一定的输入条件下,输出的结果是可以预测的,这样我们就可以给出系统的预期行为或预期结果,常常用于测试用例设计中。
图1 可控制性和可观察性的示意图
在此基础上,你可以重新理解一下可测试性:通过被测系统提供的接口对系统进行操作,然后,能够通过输出的结果了解到系统的状态是否符合预期。
如果一个系统缺乏可控制性和可观察性,如图 1 所示,可以通过增加接口的方式,使之可以控制它或产生输出。在敏捷测试中,没有自动化测试万万不行,而自动化测试对可测试性提出更高的要求,因为自动化测试更需要明确的输入和输出,所以要求系统能够提供可以调用的接口,通过接口调用来驱动程序运行并验证返回的结果。接口可以是 UI 界面或 UI 下面那一层面向业务的 API, 也可以是微服务模块暴露给其他模块的 API。
AI 软件的可测试性就是一个挑战,因为缺乏可预见性,不太容易事先定义好 Test Oracle 来验证输出结果是否符合预期。比如,Google 在 2018 年公布了用于 NLP(Natural Language Processing,自然语言处理)技术的 BERT(Bidirectional Encoder Representations from Transformers)模型,这个模型通过超过 3 亿个参数提供了强大的语句预测能力。但是,模型处理的结果需要人类去综合判断分析是否正确,并且需要理解参数微调带来的变化。不过,目前人们也在探索如何解决这些问题,第 44 讲会讨论 AI 软件的测试设计与执行。
需求、设计和代码等不同层次的可测试性
对可测试性的需求不仅仅是能够进行自动化测试和手工测试,既然我们要测试左移,那可测试性应该包括三个方面:需求的可测试性、设计的可测试性和代码的可测试性。
需求的可测试性,包括所定义的功能正确性是否能够被判断?输入、输出的数据是否有清楚的定义从而能验证其精准性?系统的性能、可靠性等是否有验证的标准和方法?
设计的可测试性,是指通过设计来确保系统的特性具有可控制性、可观察性。另外,需要通过设计确保系统的简单性。从测试角度来说,设计越复杂,系统各组件之间的耦合度越高,对单个组件的测试难度就越大。
代码的可测试性,一方面要保证需求和设计的可测试性得到实现,比如各项功能需求得到了满足,并且按照设计实现了系统的松散耦合、模块之间的接口定义。
用户故事的可测试性和 ATDD
敏捷开发提倡“可工作的软件胜于完备的文档”,强调开发团队成员之间通过沟通来澄清需求,这就造成文档质量不高、需求描述不清楚。敏捷采用用户故事的形式对需求进行描述,一个用户故事(User Story)通常按照下列的格式(模板)来表述:
As a who(谁?),I want to what(什么行为?)so that why(为何?)。
(作为一个用户角色,我需要做某事,这样就能达到某个目的)
用户故事示例:
作为这家购物网站的买家,我要通过商品名称查询历史订单,这样我就能查看某个订单的详细信息了。
用户故事是以业务语言来描述的,而不是使用技术语言,比如,我要实现微服务 A 的一个 API 供微服务 B 调用,这就不是一个好的用户故事。同时,用户故事必须是可测试的,要不然怎么验证用户故事的实现是不是符合预期?
在实际工作中,也许你经常会碰到这些情况:某个用户故事的描述太简单,每个看到用户故事的人,其理解都不一样,但产品负责人(PO)可能和开发人员有过面对面的沟通,开发人员基本知道做什么,但测试人员还是不清楚,测试就追着开发问这个用户故事究竟要实现到什么程度?开发会说:别急,等过两天,我做好了给你和 PO demo 一下,就清楚了,如果你们觉得不行,我再改。看似态度很好,但测试人员能做什么吗?根本无法设计测试用例或写自动化脚本。
功能性的用户故事还好,非功能性的用户故事不可测问题更大。 例如,用户故事描述是这样的:作为一个注册用户,我希望能够快速地登录到系统内。那么,你会问,究竟多快算快?1 秒钟还是 3 秒钟?
为了保证用户故事是可测试的,需要对用户的实际需求有一个明确的说明,这就是用户故事的验收标准(Acceptance Criteria,AC)。比如,你阅读上面那个用户故事示例,会有很多疑问:
查询历史订单,能够查询多长时间的历史订单?最近一年的,还是没有时间限制?
支持不支持模糊查询?
如果查到多个,如何显示?
如果没有查到,给出什么提示?
查看订单的详细信息,究竟包含哪些项?
有这么多问题不明白,验收标准就是要让这些问题明白,因此为这个用户故事示例增加相关的AC:
缺省是查询过去一年的历史订单;
如果没查到,问用户是否选择一个查询的时段;
支持模糊查询;
按匹配度来排序,而不是按时间排序;
每页最多显示十个记录;
查到后只显示订单号、商品名称、价格和日期;
如果想继续看,再点击订单查看;
……
这样,所要实现的用户故事就清楚了,也可以验证了。如果没有明确的验收标准,就没有用户故事的可测试性。用户故事的验收标准不仅让测试有据可依,而且由于列出了各种条件,开发人员在实现用户故事时就不容易犯错误,代码质量也会高得多。也正因为用户故事的验收标准澄清了需求,避免了开发和测试的争吵,有利于开发和测试的协作,而且减少了沟通成本,可以提高开发效率。
验收测试驱动开发(Acceptance Test Driven Development,ATDD)就是在开发设计、写代码之前,先明确(定义)每个用户故事的验收标准,然后再基于用户故事的验收标准进行开发。这样有利于我们之前强调的“质量是构建出来的”、“预防缺陷比发现缺陷更有价值”。
ATDD 与 TDD(UTDD)的关系
测试驱动开发(Test Driven Development,TDD)是指测试先行的理念,即测试在前、开发在后,提倡在编程之前,先写测试脚本或设计测试用例。TDD 在敏捷开发模式中被称为“测试优先的编程(Test-First Programming)”。
TDD 中测试先行的理念是为了给开发人员对编写的代码有足够的信心,代码的错误可以通过测试来验证。敏捷开发往往是快速迭代,程序设计不足,所以经常需要不断重构代码,而重构的前提是测试就绪,这样重构的质量就可以通过运行已有的测试得到快速的反馈。所以,有了 TDD,程序员就有了勇气进行设计或代码的快速重构,有利于快速迭代和持续交付。
重构是指在不改变代码外在行为的前提下,对代码做出修改。——《重构:改善既有代码的设计》
ATDD 和 UTDD(Unit Test Driven Development,单元测试驱动开发)都属于 TDD 思想指导下的优秀实践,可以看作是 TDD 具体实施过程的两个层次:
ATDD 发生在业务层次,在设计、写代码前就明确需求(用户故事)的验收标准。
UTDD 发生在代码层次,在编码之前写单元测试脚本,然后编写代码直到单元测试通过,这里的 UTDD 相当于传统概念(如极限编程)的 TDD。
在开发实践中,UTDD 在备受推崇的同时,也受到了广泛且持久的争议。 David H.Hansson 是著名的 Web 开发框架 Ruby On Rails 的开发者,他在 2014 年写了一篇文章对 TDD 提出了公开的质疑和否定,该文章是“TDD is dead. Long live testing”(TDD 已死,测试永生)。这篇文章一出来就引起了广泛的讨论,赞成者认为说出了自己的心声,当然也趁机表达了自己的意见,比如,工期紧、时间短根本来不及写单元测试;TDD 对开发人员的要求过高,推行的最大问题在于很多开发人员不会写测试用例,也不会重构代码。
反对者认为 David 对 TDD 的理解是片面的、不正确的,认为 TDD 就是 UTDD,而忽略了 TDD 还包括 ATDD。这里暂时不对 UTDD 进行详细讨论,但就目前的情况来看,UTDD 虽然没有死,但推行的也不好。我在今年年初公布的 2019 年软件测试调查结果中显示,只有 21% 的团队在做静态代码分析、单元测试,更别提更加高大上的 UTDD 了。
而相比 UTDD,面向业务层面的 ATDD 推行起来就比较容易,而且是必须的。因为需求模糊、需求不具有可测试性,你能接受吗?模糊的需求往往意味着返工和浪费,没有可测试性也就意味着无法开展测试。 所以,团队按照验收标准来实现用户故事,是不是理所当然?
到这里,这一讲的内容就讲完了,我简单总结一下:
系统的可测试性包括可观察性、可控制性和可预见性;
明确用户故事的验收标准是让用户故事具备可测试性的关键,也是必要的;
把 TDD 看成是“测试先行”的理念,这样 ATDD 和 UTDD 则是其两个不同层次上的落地实践;
相比 UTDD 引起的争议和推行的困难,ATDD 更容易在实践中落地。
最后出一个思考题:你所在的公司里是在推行 UTDD、还是 ATDD 呢?有哪些挑战和优秀的经验可以分享?欢迎留言讨论。
上一讲介绍了用户故事的可测试性及 ATDD。这一讲为什么不继续讲测试,而要讲解产品价值分析呢?首要原因是由于我们提倡业务驱动测试,希望从业务的角度出发来进行测试分析与设计,然后再回归业务。
其次,当一个项目开始进行测试时,要清楚项目的上下文,这是第 3 讲提到的敏捷测试“上下文驱动”的思维方式,我们应该基于上下文进行测试需求分析、设计并制定测试计划。其中,产品和业务是最重要的测试上下文之一。
再者,敏捷特别强调交付“价值”给客户,团队必须做对客户有价值的事情。所以,无论是开发还是测试,都需要关注产品的价值。测试具有保证质量的责任,之前谈质量,更多是从质量模型所定义的质量特性(比如功能、性能、安全性等)出发;而在敏捷中,从客户价值出发更有意义,所以这一讲,我们就来讨论产品价值分析。
产品价值是基础
产品价值是软件研发的基础,用户只有认可产品的价值才会购买并使用它。敏捷团队首先需要了解的是产品可以带给用户什么样的价值,以及谁才是目标用户;其次才是需求分析和功能特性的实现。然而在实际工作中,研发团队往往不太关心公司要做一个产品的目的是什么,只知道是由产品经理给出的建议,由高层领导来做决定,最后落实到研发团队。
根据 PMI(Project Management Institute)发布的年度报告,在 2017 年有 14% 的 IT 项目宣告失败,其中有 39% 是因为不正确的产品需求导致的,需求问题是项目失败的首要原因。为什么会这样呢?我们可以看看图 1,它用漫画的形式形象地描述了客户的需求是如何一步步走样的,最后改的面目全非。参与项目的每个角色对需求的理解都不一样,需求文档又很简单,客户的需求主要靠角色之间的沟通和交流来传递,挺像敏捷开发的场景,所以往往最后做出来的东西和客户想要的结果有很大偏差。
图 1 被误解的用户需求(图片来源:维基百科)
有意思的是,第一张图里客户描述的需求和最后一张图里所揭示的客户真正的需求也不一样,正如乔布斯所说,客户其实不知道自己真正需要什么。但根本的原因在于一开始就没有挖掘出客户真正的需求,福特汽车公司的创始人亨利福特曾经说过:“如果我当年去问顾客他们想要什么,他们肯定会告诉我需要一匹更快的马。”客户真实的需求是一匹马吗?不,他们真正的需求是“更快的交通工具”。
由此可见,理解用户真正想要什么进而交付满足需要的产品,并不是一件容易的事,也不要假设产品经理要求你实现的功能一定是客户真正想要的。在做产品之前,多问问用户为什么需要这个产品或者某个功能特性,也许可以帮助纠正需求的偏差或者发现被忽略的隐性需求。
今天我来介绍三款工具,可以帮助我们了解产品价值和产品定位,以便更好地进行需求分析和管理。它们都有一个共同的特点,那就是一页纸可以包含所有的内容,特别适合敏捷开发模式,也特别适合敏捷团队进行需求方面的沟通和澄清,是非常有效的沟通工具。
商业画布
商业画布的概念来自《商业模式新生代》这本书,全称应该是商业模式画布(Business Model Canvas)。它是一个适合敏捷的商业模式分析工具,可以帮助研发团队快速地对产品的价值和市场定位,有一个整体的认识。
商业模式定义:
商业模式描述了企业如何创造价值、传递价值和获取价值的基本原理。——《商业模式新生代》
商业画布由 9 个模块组成,如图 2 所示。我们通常用它来做企业的商业模式分析,每个模块其实有很多种可能性和替代方案,分析的过程就是从这些可能的方案里找到最佳的组合。好的商业模式往往不是一蹴而就的,需要在经营过程中不断调整,比如阿里巴巴最早的业务是 B2B 形式的,但今天更有价值的是淘宝天猫、蚂蚁金服等。一旦企业形成极具业务价值的商业模式,即使企业在短期甚至长期都不能盈利,但仍然也能受到追捧,比如亚马逊,因为我们相信好的商业模式下,赚钱是迟早的事。
研发团队可以用商业画布去收集相关信息来做为需求分析的输入。如果能邀请产品经理坐下来一起完成一个产品的商业画布,一边制作,一边沟通、澄清,效果更好。
图 2 商业画布构造图
用商业画布进行商业模式分析的过程基本如下。
找到产品的目标用户群(客户细分) → 分析用户的需求(价值主张)是什么 → 探讨怎样才能获取到用户(渠道通路) → 怎样建立和维持客户关系留住客户(客户关系) → 该用什么样的方式实现盈利(收入来源) → 发掘产品目前拥有什么样的核心资源,比如资金、技术、人力等(核心资源) → 列出必须要交付的业务功能(关键业务) → 找出重要的合作伙伴都有哪些(关键合作伙伴) → 分析投入产出比是怎样的(成本结构)。
图 3 就是一个在线教育 App 产品的商业画布示例。从测试角度来看,我们应该重点关注其中的“客户细分、价值主张、客户关系、关键业务、渠道”等五项内容。
图 3 在线教育 App 商业画布示例
影响地图
影响地图(Impact Mapping)是《影响地图:让你的软件产生真正的影响力》这本书提出的一个用于业务分析的可视化工具,它从 Why-Who-How-What 这四个方面,按照“目标——角色——角色的影响方式——具体方案”的顺序进行讨论,逐步提取出达成业务目标的解决方案。
利用这个工具,研发团队从“为什么要做这个产品或者功能”出发,和业务负责人一起讨论并制定一个产品或功能要实现的业务目标,然后识别出哪些角色会影响这个目标的实现,影响方式是什么,每种方式具体要做什么。研发团队可以从中识别出产品需要做哪些功能特性,同时帮助实现业务目标。
我以上面的在线教育 App 产品为例来讲解影响地图的使用。假设产品经理希望添加一个课程分销的功能,App 用户可通过这个功能把课程推广出去,并获得收益。
它的 Why-Who-How-What 四个方面内容如下:
Why:包含两个方面的内容,第一,作为研发团队要了解为什么需要这个功能?比如“分销功能“是为了实现课程推广的裂变效应,从而提高销售额;第二,通过这个功能可以实现什么样的业务目标?设定的业务目标应该是明确、清晰、可衡量的,并且是可以实现的,比如在三个月内课程销售额通过分销功能增加 20%。
Who:为了达成目标需要影响到哪些角色?这些角色既可以帮助我们实现业务目标,也可能是阻碍目标的实现。在此案例里,识别出影响目标的角色包括:App 用户、市场推广人员、课程审核人员、课程讲师等。
How:表示需要用什么样的方式影响上述角色的行为来达成目标,既包含产生促进目标实现的正面行为,也包含消除阻碍目标实现的负面行为。
What:对于每一种影响方式,需要采取哪些具体方案。
图 4 就是为这个分销功能制作的一个影响地图的示例。
图 4 影响地图示例
从这个例子的具体方案中,是不是可以识别出不少需要研发团队实现的功能特性?比如推广海报的制作、收益查看、提现和微信链接等。如果分析出来的功能特性比较多,团队需要对它们进行优先级排序,按照功能特性对业务目标的影响大小来决定哪些功能必须要有、哪些功能无关紧要,哪些功能要先做、哪些可以后做等。
同时,通过影响地图,研发团队可以清晰的知道产品或功能如何帮助企业实现业务目标,这样就会对自己做的产品更有信心,工作也会更有动力。
(用户)故事地图
使用商业画布和影响地图可以帮助团队明确产品的价值、目标、用户、主要功能特性等问题,这些问题解决之后,接下来研发团队就可以开始编写用户故事。一般情况下,先编写史诗级的用户故事(Epic),然后将每个 Epic 拆分成若干个用户故事。下一讲将会详细介绍如何进行用户故事的拆分和评审。这些不同的用户故事组成要实现的软件功能特性的待办事项(Backlog)。但这样形成的 Backlog 所呈现的用户故事是零散的,因此呈现的需求也是零散的,缺乏系统性。这时就需要引入用户故事地图(User Story Mapping)——一种生成用户故事的团队协作沟通的新方法。
用户故事地图的概念来自 Jeff Patton 创作的一本书,书名就叫《用户故事地图》,敏捷团队可以用它来协作产生用户故事,也可以用来进行需求分析管理。用户故事地图可以为敏捷团队解决下列问题:
团队协作产生用户故事;
系统化地呈现软件要提供的全部功能;
识别出要交付软件的 MVP(Minimum Viable Product,最小化可用产品),目的是以最小的投入快速交付对用户最有价值的软件,这一点对于敏捷开发尤其重要,可以很好地服务于迭代增量开发;
呈现不同粒度的用户故事之间的关系(Epic 和用户故事);
识别用户故事的优先级。
用户故事地图需要敏捷团队成员共同完成,可以采用大家坐在一起进行头脑风暴的方式。我还是以在线教育 App 这个案例来设计一个用户故事地图,如图 5 所示,它从购买课程的用户角色出发,按照课程购买发生的活动顺序从左到右排列。最上面一行是 Epic 级别的用户故事,下面是每个 Epic 拆分出来的细粒度的用户故事,从上到下显示了用户故事的优先级,按照轻重缓急把用户故事分成三批进行交付,最有价值的用户故事放在第一批,次要的放在第二批,其他的放在第三批。这样,第一个可交付软件的 MVP (第一批用户故事)就出来了。
在一个软件产品中,不同用户类型对应不同的用户故事地图,对于在线教育 App,比如也可以给“专栏讲师”这个角色制作一个用户故事地图。
图 5 用户故事地图示例
今天的课程就讲到这里了,我来对所讲的内容做一个总结。这一讲主要介绍了三个工具,从价值和商业模式分析、如何对产品的某个功能深入分析,以及如何就功能特性及其优先级等进行有效沟通这三个方面进行了讲解。我把其中的重点总结如下。
了解“为什么要做一个产品“是做出具有价值的软件产品的基础,研发团队可以用商业画布来对产品进行价值分析。
影响地图可以帮助团队进行产品或某个功能的业务分析。从要实现的业务目标开始,按照 Why-Who-How-What 的顺序分解出要实现的具体功能特性,同时也能帮助我们整体了解实现业务目标的方式和途径。
用户故事地图是一个团队协作和需求管理的工具,研发团队用来生成用户故事的同时,对要交付软件的功能特性以及用户故事的优先级也有了一个清晰、完整的理解和共识。
最后给你留一个动手的练习:针对你所参与的软件产品,整理出一个用户故事地图,并思考是否需要增加新的用户故事?
传统的需求评审是通过评审会议,产品、开发、测试等各路人马坐在一起来完成市场需求文档(Market Requirements Document,MRD)或产品需求文档(Product Requirements Document,PRD)的评审,以发现如需求缺失、无意义的需求、模棱两可的描述等问题。
通用的评审标准
一般在评审前,需要明确评审标准,使评审有据可依。在一定程度上看,无论是传统的需求评审,还是敏捷需求评审,有些标准是通用的,具有普适性,例如在第 20 讲中所提到的需求可测试性,就是通用且必要的。需求评审标准还包含可行性(能够实现)、易修改性(文档容易维护)、正确性、易理解性、一致性等,有些要求,比如正确性、易理解性,看似简单,要做到也是不容易的,我们针对其中一个可以提出一系列的问题,想一想我们平时的需求文档是否都能给出明确、正确的回答?恐怕很难吧?
1. 正确性:
需求定义是否符合软件标准、规范的要求?
每个需求定义是否都合理,经得起推敲?
是否所有的功能都有明确的目的?
是否存在对用户无意义的功能?
所采用的算法和规则是否科学、成熟和可靠?
有哪些证据说明用户提供的规则是正确的?
2. 一致性:
所定义的需求内容前后是否存在冲突和矛盾?
是否使用了标准术语和统一形式?
使用的术语是否是唯一的?
所规定的操作模式、算法和数据格式等是否相互兼容?
除了正确性、易理解性、一致性、可测试性、可行性和易修改性之外,还需要考虑需求的完备性和可追溯性,这两点更有挑战,要做到完备几乎是不可能的,在敏捷中也不追求功能特性的完备性,而且是先交付高价值的特性,再交付中等价值、低价值的特性。但是,就第 20 讲提到的用户故事的验收标准,就需要考虑其完备性,尽量考虑或挖掘出各种输入 / 输出、条件限制、应用场景或操作模式,不仅包括正常的输入 / 输出、应用场景和操作模式,而且还要包括非法的输入 / 输出、异常的应用场景和操作模式等。针对数据项,甚至需要考虑其来源、类型、值域、精度、单位和格式等。
需求的可追溯性主要指每一项需求定义是否可以确定其来源,比如:
是来自哪项具体的业务?
由哪个用户提出来的?
是否可以根据上下文找到所需要的依据或支持数据?
后续的功能变更是否都能找到其最初定义的功能?
功能的限制条件是否可以找到其存在的理由?
Epic 的评审
对于敏捷项目,需求如何评审呢?一般敏捷的用户故事也是由特性拆解出来的,一个特性可以拆出很多个用户故事。从具体评审来看,就是要评审特性的描述和用户故事的描述,但在敏捷中,可能没有具体的特性描述,或者说,特性的评审属于传统的范围,可以按照上面讨论的通用标准来进行评审。而在敏捷中,面对的两个具体评审对象是用户故事和 Epic。我们可以先从宏观的——Epic 开始,再到微观——具体的每一个用户故事。
Epic 在敏捷中应用还是比较普遍的,但不同的人对 Epic 理解是不一样的,容易引起一些争议和混乱。Epic 最早由 Mike Cohn 在其《用户故事与敏捷开发》(User Stories Applied to Agile Software Development)一书中提出来,即“When a story is too large, it is sometimes referred to as an epic”。这里,Epic 是史诗般的大用户故事,也符合 Epic 这个词的本意。例如,说一部“动作冒险电影”,自然少不了一场汽车追逐、一场格斗或一场枪战等;上一讲提到的在线教育 App 的案例,自然也会有发现课程、购买课程、分销课程等这样的大故事。
但也有人(如 Atlassian 公司)认为 Epic 是一种与用户故事分开但包含用户故事的积压项目:Epic 是大块头的工作项,它可以分解为许多较小的用户故事(An epic is a large body of work that can be broken down into a number of smaller stories),可以看作图 1(即上一讲所描述的用户故事地图——图 5)的一列,即一组用户故事。
图1 用户故事地图示例
总而言之,把 Epic 看成更大的故事,这没错,可以用图 2 来区分特性、Epic、用户故事和任务(task)之间的关系。
图2 特性、Epic、用户故事和任务之间的关系
下面就来讨论 Epic 的评审,还是以在线教育 App 为例,从购买课程的用户角色出发,按照课程购买发生的活动顺序从左到右排列,从账户管理开始,到课程搜索、课程购买、课程管理,最后以课程分销结束。评审时,我们会提出以下一些问题:
这个过程合理吗?符合时间顺序吗?
Epic 名称是否合理?
Epic 每个特性下面的用户故事(列)设置合理吗?
优先级设置是否合理?
例如,比如 图 1,从购买课程的用户角色来看,是否能发现这些问题:
账户管理是不是可以往后排一排?
“课程搜索”后是否要增加“试读”内容?取“课程发现”名称是不是更贴近用户?
“综合查询”比较模糊,也没必要,是不是可以改为“关键词查询”更明确些?
“课程管理”是否改完“课程学习”更为合理?
“收益管理”也偏大,是不是可以拆成两个故事“收益提现”和“收益详情”?
拼团购买,目前比较流行,也能吸引更多新用户参与。
课程分享再增加一个“影响力榜”,会吸引更多用户参与。
课程学习后是不是要做学习笔记,发现某一讲很好,是否要收藏?
如果要“管理课程”,是否要分为“已购课程”和“未购课程”或“已购课程”和“所有课程”。
…..
经过评审之后,修改评审中所提出的问题,就形成了新的、更合理的用户故事地图,如图 3 所示。
图3 评审通过的用户故事地图示例
用户故事的评审
完成 Epic 的评审,就可以进入用户故事的评审。用户故事的评审相对具体、有标准,最常用的标准就是 INVEST:
INVEST 原则:
Independent(独立的),Negotiable(可协商的),Valuable(有价值的),Estimable(可估算的),Sized Appropriately or Small(大小合适的),Testable(可测试的)
这个 INVEST 清单最早起源于 Bill Wake 在 2003 年写的文章 INVEST in Good Stories, and SMART Tasks,该文章也将首字母缩写为 SMART(特定的、可衡量的、可实现的、相关的、限时),重新用于用户故事的技术分解所产生的任务。他还在 2012 年,写了 6 篇系列文章:
Independent Stories in the INVEST Model
Negotiable Stories in the INVEST Model
Valuable Stories in the INVEST Model
Estimable Stories in the INVEST Model
Small – Scalable – Stories in the INVEST Model
Testable Stories in the INVEST Model
其中以独立性这篇文章为例,他举了一个极端的例子,想象一组功能,具有 6 个能力:{A, B, C, D, E, F}。
如果我们也写了 6 个用户故事,分别覆盖了其中一些功能,比如:
{A, B}
{A, B, F}
{B, C, D}
{B, C, F}
{B, E}
{E, F}
这种重叠(耦合)是不是很痛苦?所以一定要让每个用户故事相对独立,这样不仅有利于实现,也有利于理解和沟通。
当初是为了整成一个容易记住的单词,所以 INVEST 由每个单词的首字母组成。从评审的过程看,先看这个用户故事有没有价值,这种价值是指对客户 / 用户的价值,而不关心对开发人员或测试人员有没有价值;如果没有价值,其他的 INEST 就不用看了。所以,评审的过程是从是否有价值开始,再检查其独立性、可协商性、可测试性、可估算等,如图 4 所示。
图4 用户故事评审过程示意图
一个用户故事越大,其估算的误差就越大,而可测试性在第 20 讲中已介绍,所以可估算、可测试性等比较容易理解,这里就不再说明了。
不容易理解的是“可协商的(Negotiable)”,因为我们之前说,需求越明确越好,不希望出现“快”、“大概”、“可能”、“几乎”等这样的词,越模糊就越不可测。可协商,是否意味着不明确呢?其实不是,只是说,用户故事不是功能的约定,将来是可以调整的,即用户故事的细节在未来开发过程中可以由客户和开发人员去协商,包括测试思路。
“可协商的”要求和可测试性是矛盾的,而且和我们之前说的 ATDD 也是有冲突的。如果在设计、编程之前就把用户故事的验收标准等明确下来,在未来开发过程中协商的空间就很小了。
再者,如果用户故事越明确,不仅有利于可测试性,也有利于估算。规模小,有时具有迷惑性或欺骗性,搞清楚更为重要;太小,也不一定是好事情,Mike Cohn 和 Kent Beck 合著的《用户故事与敏捷方法》提到了故事太小是第一个不良征兆,还举了一个例子,即下面的两个用户故事:
搜索结果可以保存为XML文件
搜索结果可以保存为HTML文件
就应该合并为一个用户故事。用户故事的大小可以控制在几个人天的工作量,甚至 Bill Wake 认为故事通常最多只能代表几个人周的工作量。图 2 描述的场景算是通常情况,或者系统规模不大的情况,用户故事的工作量是几个人天;如果是大规模系统,用户故事的工作量可以达到几个人周。
至于说,用户故事需要满足 3C 原则:Card(卡片)、Conversation(会话)和 Confirmation(确认)等,即:
卡片:卡片的空间很有限,促进用户故事简洁,捕获需求的精髓或目的。
会话:类似前面“可协商的”,强调需求的细节是在开发团队、产品负责人及利益相关人之间的会话中暴露和沟通的,用户故事仅仅作为建立这个会话的一个承诺。
确认:用户故事还要包含满足条件形式的确认信息,这些就是之前所说的、用于澄清期望行为的验收标准。
从用户故事需求评审来看,还有一点要注意,它是表达客户的真实需求还是表达客户给出的解决方案。我们需要的是客户的真实需求,而不是解决方案,解决方案倒是我们开发团队所要做的事情。
我经常会举一个简单的例子,你去食堂吃饭,你的同事在路上碰到你,说:帮我买几个馒头回来,我就不去食堂了。“买几个馒头”是用户的需求,还是用户基于某个需求给出的解决方案呢?他的真实需求可能会是哪些呢?这算是留给你的思考题,欢迎留言讨论。
设计评审的价值和重要性
与需求评审不同,传统开发模式下测试人员很少参加设计评审,总觉得和测试的关系不大,其实,这样的认知是错误的。
记得在一次会议演讲之后,一位与会的测试同学提问:如何进行可靠性测试?
如果是你的话,可能会将可靠性计算公式告诉他,然后告诉他用压力测试、故障注入的方式来进行可靠性测试,你甚至可能想到今天比较时髦的混沌工程,但这并不是最有效的方法。
更好的做法是这样:我会告诉他,首先要加强设计评审,在评审时,问系统架构师或系统设计人员,他们是如何来保证系统可靠性的?关键组件有冗余设计吗?故障转移机制有没有?如果有故障转移机制,又是如何设计的?可以在线上进行演练吗?系统一旦失效,估计花多少时间恢复?用户数据是实时备份的吗?用户数据是否有被攻击的漏洞?线上数据库遭受破坏,数据能恢复吗?数据恢复需要多长时间?了解了这些可靠性相关的信息后,验证这些具体措施就相对容易得多。
也就是说,我们需要先通过设计评审,当检查系统设计时是否充分考虑了可靠性的需求,在设计中是否存在考虑不周的问题,并且通过设计评审,清楚如何去验证系统的可靠性;否则,使用压力测试的方法进行高负载测试,无论是从时间上还是从要付出的其它代价上来看,往往很难让人接受。即使有故障注入的测试方法,也需要先了解可能有哪些故障触发点或故障模式,才能把有效的故障数据注入进去。
更糟糕的是,如果没有设计评审,或在设计评审时,没有从测试的角度去提问,可靠性、性能、安全性等问题就很难在系统设计时被发现。等到后期系统测试时再发现,往往为时已晚,团队将付出很大的代价,需要修改设计和代码,再重新测试。
这就体现了设计评审的价值和重要性,通过设计评审,可以给我们带来下列 3 点收益:
更好的确保软件设计的可测试性,包括系统的功能、性能、安全性、可靠性等可测试性。
更重要的是能够提前发现设计上的缺陷,避免直到系统测试时才发现问题,大大降低了系统的质量风险、项目管理风险和软件研发成本。
更好地了解系统是如何实现的、由哪些组件、服务构成,更深入地了解系统架构、关键组件、关键接口等,有助于实现分层测试、面向接口的测试,从而提高测试方案、测试设计的有效性和效率。
在敏捷开发模式下,我们更强调测试左移和持续测试,这就少不了设计评审。从设计评审的角度看,无论是传统开发还是敏捷开发,都有一些基本的评审标准,比如设计的规范性、开放性、可测试性、可扩展性和一致性等,设计应力求简单、合理、清晰,做到高内聚、低耦合。同时,最终设计必须能满足需求,所有功能特性都有相应的组件去承载,并且它们之间的映射是合理的。
下面就来说说如何高效地完成敏捷开发模式下的设计评审。
如何完成架构评审
在敏捷开发模式下,文档再省,系统架构图不能省,其次,接口定义文档也不能省。所以在敏捷开发模式下,设计评审重点应放在架构评审和接口定义文档的评审上。
首先,我们摊开系统架构设计图,如图 1 所示,针对这个架构图先整体评审,再对每层进行 Review,从UI(User Interface)开始,深入到 API 层、安全层(Security Layer)、核心(Core)层、存储(Storage)层、工作流引擎(Workflow Engine)、搜索引擎(Search Engine)、目录和元数据引擎(Catalog & Metadata Engine)、状态与报告(Status & Reporting)等组件。
图1 某软件系统逻辑架构示意图
在系统架构设计评审时,如本讲开始所说的,可以就性能、可靠性、安全性等进行提问,了解系统整体架构设计是否合理、是否有缓存机制、是否有冗余设计、是否存在单点失效等问题;在整体上,则需要了解选型是否合理、是否是分布式架构、是否更应该选用微服务架构等问题。如果具体到非功能特性,也有以下这些原则或规律可循。
性能:一般在设计上会考虑分层架构、分布式架构、(文件)系统缓存机制、轮询式任务分配、数据服务器和应用服务器分离、数据分片和数据读写分离、CDN 等措施,比如,现在普遍采用的 CDN(Content Delivery Network,内容分发网络)技术,能够将站点内容发布至遍布全国的海量加速节点,使其用户可就近获取所需内容,避免网络拥堵、地域、运营商等因素带来的访问延迟问题,有效提升访问速度、降低响应时间,获得流畅的用户体验。
安全性:数据和系统的分离、将系统权限和数据权限分别设置、基于角色的访问控制设计等都可以提高系统的安全性。如图 1 所示,设计中间层可以隔离客户对所存储数据的直接访问,以进一步保护数据库的安全性。
可靠性:采用多节点分布式体系架构,这样单个节点失效不会造成整个系统失效,从而确保系统的可靠性。而且,负载也能平衡地分布在不同节点上,不会像单体系统受到集中式负载压力冲击而引起类似“拒绝服务(DoS)”的问题,以保证服务的可用性。
可扩展性:区分可变和不可变业务,采用多层分布体系架构,基于不同的组件或层次为不同的业务提供开放的服务接口(API、SDK),并尽可能简化架构,降低模块间的耦合性等。
有层次的(组件)评审
在整体评审通过后,我们会继续就系统的各个层次或各个组件进行更为细致的评审,逐层往前推进。
UI 层是否采用了类似 GWT(Google Web 工具包)的 Web 2.0 用户界面框架,从而能够支持 Firefox、IE、Safari、Google Chrome 等浏览器的最新版本,以及是否适用基于 JQuery Mobile 的移动设备、WebDAV 和 CIFS 协议等。
API 层,检查是否支持 OASIS 开放标准的 CMIS(Content Management Interoperability Services)协议,即是否允许使用 Web 协议互连,并控制各种文档管理系统和存储库;检查是否支持 OpenAPI 标准,能否通过 Web 服务(SOAP)和 REST 提供完整的、开放的 API,从而实现与第三方应用程序的集成;检查是否提供了用于 Java、.NET 和 PHP 等二次开发的 SDK。
安全层,涉及用户身份的验证、注册用户和用户访问权限的管理、安全控制等。如图 1 所示,该架构采用了 Spring Security 实现基于用户的凭据集中管理用户的访问权限、通过 Access Manager 模块实现安全控制、通过 CAS(Authentication Centralized Service)服务实现身份验证等。
存储模块是否足够安全、可靠和开放。图 1 的系统架构使用了 Hibernate 进行 OMR(Object Relation Mapping)数据映射,并能支持不同的关系数据库,而整个元数据层则存储在 DBMS 数据库中。
其他组件,比如搜索引擎、目录和元数据等组件设计是否合理,如果选择第三方开源产品,那么它是否是成熟的组件?属于哪一类开源许可协议?有没有法律风险等?
接口定义的评审
架构评审完之后,就需要深入到各个组件详细设计的评审,而在这之前,需要针对接口详细设计文档进行评审,这也是值得我们特别关注的,因为接口关系到每个开发人员能否相对独立、高效的工作,关系到之后众多组件能否集成为可正常运行的系统。
接口也分为多种,比如资源接口、操作接口和页面接口。前面两种接口相对简单,要求按照 RESTful 方式定义即可,而对页面可能会涉及太多接口,不能一个个地调用, 这样会导致系统性能比较差,严重影响用户体验。所以我们需要在后台把数据处理好,然后形成一个聚合型接口提供给前端来使用。
从接口定义看,要求遵循 RESTful 风格、采用 UTF-8 统一接口编码方式,接口应具备可扩展性,接口拆分合理、粒度合适,接口描述清晰、一致,标注请求方式并能区分 GET、POST 等不同方式的应用场景,比如获取数据用 GET,新增 / 修改 / 发送数据用 POST;接口地址(URL)使用相对路径,从而尽量减少参数传递;参数命名准确并符合统一的命名规则,同时标注参数数据类型、值域范围、是否可为空等;接口必须提供明确的数据状态信息、统一的标识调用状态(无论成功还是失败)。
从接口性能看,数据格式采用 JSON 格式比 XML 好,这是因为它的数据量少,而且尽量按需传递数据,前端需要什么数据就返回什么数据。为了更好的性能,还要设置缓存机制,包括文件缓存、Memcache 缓存等。
从安全性看,包括验证签名机制、接口访问授权机制、数据传输加密、客户端身份验证和时间戳验证等,包括选用合适且安全的算法。对于核心数据的 ID 字段, 不要使用自增的数字类型, 而使用 Hash 算法产生随机的字符串类型,避免核心数据被轻易抓取等。
设计的可测试性
可测试性不仅要从需求开始抓,设计环节也同样重要,人们经常提到“可测试性设计(Design for Testability,DFT)”、“设计驱动开发(Design-Driven Development,DDD)”。通过设计可以确保系统结构的简单性、可观察性和可控制性,如 MVC 设计模式、接口单一性、各个模块有明确的接口定义等。在设计上改善软件的可测试性,主要是通过设立观察点、控制点、驱动装置、隔离装置等来实现。
测试驱动设计方法,比如先确定验收测试用例,再设计具体的功能;先确定性能、可靠性等测试用例,再考虑如何实施架构设计,以满足不同特性的要求。
选用开放、先进而成熟的设计模式和框架,在一定程度上能保证系统结构的低耦合性、单一的依赖关系,具有较高的可测试性。
可控制性设计,包括业务流程、模块、场景、全局变量、接口等的可控制性设计,即在外部提供适当的方法、途径,直接或间接地来控制相应的模块、全局变量和接口等。这些途径可能包括设立 XML 配置文件、暴露 API 接口、统一接口等操作。
数据显示与控制分离,通过分层,增加了系统的可观察性和可控制性。这样,就可以通过接口调用,分别完成相应的业务逻辑、数据处理等的测试。
遵守设计原则(如接口隔离原则),并针对模块,尽量分解到相对稳定、规模合适的程度,以确保模块的独立性和稳定性,有利于独立开展对模块的测试活动。
设计易理解性,包括明确的设计标准、规范的设计文档、明确的接口及其参数的定义,使设计有据可依,层次清晰,设计文档易读。
关于设计评审就介绍到这里,总结一下这一讲的内容,概括为下列 4 个要点:
非功能性的质量在很大程度上取决于系统的设计质量,在设计评审时,我们要善于质疑和提问,不仅能获得更好的可测试性,而且也能及时发现设计上的缺陷。
设计评审先从整体架构设计评审开始,把握全局质量,包括系统的性能、安全性、可靠性等,再分层评审,逐步深入到各个组件的评审。
今天面向接口的设计和编程占比越来越大,微服务架构也逐渐流行,要加强对接口设计文档的评审,接口设计评审要关注的细节还比较多,需要认真对待。
设计上还有一些更好的理念,如 DFT、DDD 等,值得提倡和实践。
最后给你出一个思考题:在架构设计评审中,性能、可靠性、安全性等之间也存在着一定的冲突,比如提高安全性可能会降低性能。关于这方面的冲突,你有具体的例子吗?如何解决?