用“实例化需求”,让需求澄清更高效
实例化需求不仅解决了需求分析和撰写的问题,也给出了需求沟通和澄清的方法。本文从实例化需求的定义出发,从使用实例化需求的原因、实例化需求的产生输出、自动化测试和操作流程这几个方面对实例化需求进行了说明介绍,与大家分享。
01 什么是实例化需求?
实例化需求的英文是 Specification by Example,简称 SBE,直译过来就是用实例说明需求。
实例化需求是一组方法,它以一种对开发开发团队有所帮助的方式(理想情况下表现为可执行的测试)描述计算机系统的功能和行为,让不懂技术的利益相关者也可以理解,即使客户的需求在不断变化,它也具有很好的可维护性,可以保持需求的相关性。从而帮助团队交付正确的软件产品。
为避免需求沟通过程中的「知识诅咒」,“实例化需求”方法从场景出发,以用户的操作实例来澄清需求。
相比一般的规格说明,实例更加场景化,能够激发参与和深度讨论;同时,实例是具体的,其典型形式是:「在什么情况下,做什么操作,会得到什么结果」。基于具体的实例,更加便于沟通中的双向确认,保证理解的一致和场景覆盖。
上图是对实例化需求的概念说明:
- 用例子来分析和澄清需求。
- 这些例子随后会转化为测试用例。
- 最后再通过测试验证需求。
如此形成闭环,这个三角是实例化需求的核心概念。
在「实例化需求」中,开发、测试和业务人员一起沟通需求,避免信息传递的噪音和损耗。
02 为什么使用实例化需求?
实例化需求的核心是,让项目的所有干系方进行有效的协作和沟通,用实例的方式说明需求,用自动化测试的方式频繁地验证需求,从实例化的需求说明和自动化测试用例中演进出一套“活文档系统”。这套“活文档系统”既可以有效地对系统进行说明,又可以当做交付验收的标准。
- 有效的交流沟通确保有足够的时间澄清需求。
- 使用举例的方法澄清需求能在第一时间识别出需求是否足以支撑开发。
- 所有的干系方参与需求讨论,可以确保大家对于交付哪些东西有一致的理解。
- 具有不同领域背景的干系方一同参加需求讨论,可以规避因个人认知局限带来的需求问题。
- ”活文档系统”对于变更有着先天优势,可以以最少的维护成本维持文档的相关性和可靠性。又能避免过度说明需求而产生浪费,避免花时间在开发前有可能发生变化的细节上,对于变更天然友好。
- 采用自动化测试的方法实现业务实例,代码开发出来即可以验证,无须经过冗长的手动回归测试,降低返工。
03 产生哪些输出?
实例化需求有9个过程模式:从目标获取范围、协作产生需求说明、举例说明、提炼需求说明、在不修改需求说明的情况下实现自动化验证、频繁验证、以及演化出一个活文档系统。这 9 个过程模式涉及了如下的输出(制品):目标和范围、需求说明、例子、自动化测试及活文档系统。
下面我们一个个来详细说明。
1. 澄清价值,定义目标和范围
实例化需求的第一步“澄清价值”,它包含两个子步骤:
- 描述背景。也就是需求的业务背景和系统的上下文。这一步形式相对自由。上面的示例图中,我使用了面向对象分析中的常用的系统上下文图(SCD,System Context Diagram),定义了系统的边界、所处的环境以及主要组成部分,你也可以使用更自由的线框图来表达系统的上下文。系统上下文相对稳定,并不需要对每个需求重复这样描述,只要在必要时(比如发生变化时),做出澄清就可以了。
- 澄清用户问题和业务目标。需求最终要解决用户的问题,从而实现产品的业务目标。因此,在讨论具体的需求前,我们还要澄清用户是谁,要解决他们什么问题。
针对目标和问题的典型挑战性检验是:
- 如果不做这个需求会怎么样?
- 有没有其它替代方法?
认真回答上面两个问题,往往能挖掘出需求的本质,确保我们在解决的是真正的用户或业务问题。
2. 需求说明
在实践中,我们所产生的“需求说明”更具体的体现形式是:
- 工作流
- 领域模型
- 业务规则
3. 工作流
工作流也称为业务流程或业务场景,是用户通过一系列步骤,达成系统业务目标的一种实现方式。在实例化需求工作坊中,参与人员在白板上使用顺序图、活动图、带有泳道的活动图等方式,共同绘制出工作流。当然,如果工作流足够明显,也可以直接采用业务用例的形式,列出标题,理清它们之间的先后关系即可。
简单来说,就是为了实现上面的目标,系统需要支持哪些用户操作?这些操作的流程是什么样的?具体分为两个子步骤:
1)列出用户的操作。产品的功能体现为它所支持的用户操作,列出用户操作,可以涵盖其功能性需求。如下图,我们通过用例图描述了用户的操作。用例图定义了为完成特定目标,操作者和系统之间的交互,它包含操作者和操作两个部分。
用例是需求工程中获取和列举功能需求最常用的手段之一,如果不习惯用例的表示法,更简单的方法是直接列出用户和用户的操作。
2)画出用户操作的流程步骤。这一步的目的是:定义操作的具体交互流程。下图使用了活动图和时序图两种形式,来表达操作步骤。其中,活动图形式上与流程图类似,只不过它表达的是用户操作流程,而不是技术实现流程;时序图则在表达系统间的交互方面更胜一筹,例如针对金融类系统的需求,可以用时序图表示系统间的资金往来和记录存取等。
活动图示例
带泳道的活动图示例:
时序图:
这两种表示法,都表示了操作的步骤,团队应该灵活选取适合的表达方式,当然也可以选取其它表示法如通信图、状态机等。
状态机:
用户注册通信图:
针对操作和步骤的典型挑战性检验是:
- 这些操作能解决识别出的用户问题并实现业务目标吗?
- 操作步骤合理吗?
- 操作流程可以更简单吗?
认真应对这些的挑战,将确保功能和操作的合理性、简单性和完整性。
或者就是简单地采用列表的形式,列出几个相关的用户场景:
- 新开通一个子代理商。
- 为子代理商充值。
- 为子代理商修改密码。
- 修改子代理商的基本信息。
- 查询子代理商列表。
- …
绘制工作流的目的不是为了描述,而是为了信息发现和挑战(challenge, 这里是褒义),即既有的工作流是否合理,是否存在遗漏,是否需要进一步讨论其中的业务数据和业务规则,等等。
在上面的工作流中,我们可以看到的挑战包括:
- 子代理商的初始化密码怎么设定?
- 子代理商首次登录时是否要被提示修改密码?
- 子代理商可以自己添加用户吗?
- 子代理商的基本信息包括哪些?哪些信息需要校验?
- 是否需要在创建时就为子代理商进行充值?
- 创建的子代理商可以进一步创建子代理商吗?
- …
回答上述挑战的过程,事实上是一个需求澄清的过程,它也会对应的更新工作流、领域模型和业务规则。在进行挑战和回答挑战的过程中,也一般都会包含了对每个业务流程或规则背后的业务价值的讨论,以及紧急程度的讨论。例如:
- 密码的一般性规则(例如必须包含特殊字符)是否要适用于初始密码?这会不会对线下过程带来困扰?
- 我们为什么要把联系人和管理员区分开?
- 在我们的当前阶段,能否延迟支持代理商的多管理员功能?
- …
在上述案例中,一个可能的讨论结果是:代理商的多管理员功能虽然是需要的,但是在当前阶段可以暂缓实现,等等。
4. 领域模型(统一术语)
大多数讨论都是很热闹的,但是效果却不尽相同。其实,热闹的背后是七嘴八舌还是井然有序,常常是因为概念引起的。大多数人都有过这样的经验:两个人争论的面红耳赤,但是最终发现我们说的其实根本不是一件事情!或者发现我们说的其实就是同一个观点!根本就无需争论!
在讨论中,我们常常看到有这么一种人能够打破僵局,推动讨论:“来,让我们看看每个人说的是什么”。
每个人在内心所建的概念的差异,往往是导致争论的根源。
领域模型有很多方面的价值,但是让我们首先聚焦于它所表达的概念,对业务实体和它们之间的关系进行建模。例如下面的这个领域模型:
有了上述的模型,当我们在讨论中说到代理商基本信息的时候,所有人就都知道哪些是基本信息。说到子代理商的时候,所有人都知道子代理商其实也是一个代理商。这其实就是领域驱动设计(Domain Driven Design, DDD)中所讲述的“统一语言”的应用。
同时,领域模型带来的统一语言,使得业务规则讨论变得更加精确,也更加高效。
例如:如果子代理商权限为”无“,则代理商不能创建子代理商。代理商可以查询子代理商的基本信息。
像上面的这种模型如何获得?尽管存在各种各样的方法,例如关注需求描述中的名词等等,但是可以肯定的一点是,模型是通过讨论获得的。它是持续演进的结果。这个过程常常是这样一个模式:
- 发现既有词汇不足以表述需求,或者发现参与人员在过程中产生了困惑;
- 试图提出一些关于新的词汇的假设,对既有模型进行修正和补充
- 辩驳这些假设;
- 修正,推翻,达成一致。 词汇存在歧义是一个常见的问题,这类问题如果不通过讨论,很难快速取得一致。上述模型的达成过程事实上首先是在白板上完成的。
具体领域模型使用 UML(统一建模语言)表示法,并体现为不包含操作的类图,领域模型由三个要素构成:
- 领域中的实体对象(如: 房产,房主等)和概念(如:交易,按揭方案等)。
- 对象或概念间的关系。如:房主拥有房产等,”拥有“就是房主与房产之间的关系。
- 这些对象或概念所包含的属性。如:房产的属性包含房龄、价格、面积等。
5. 业务规则
实例化需求的最重要输出是需求验收标准,它们可以表达为一条条的业务规则。上一步,我们已经列出了操作和操作步骤,用它们来组织业务规则非常合适,对于操作中的主要步骤,团队可以列出它们的业务规则。
最常见的业务规则可以表达为三段式的结构,也就是「Given(当),When(如果),Then(那么)」。比如:
- 当用户是 VIP 会员时,如果其购买金额为100元,那么运费为0元。
- 当用户是 VIP 会员时,如果其购买金额为99元,那么运费为0元。
- 当用户是普通会员时,如果其购买金额为100元,那么运费为0元。
- 当用户是 VIP 会员时,如果其购买金额为99元,那么运费为10元。
实例的形式表达场景和业务规则,这是”实例化需求“这个词的来源。之前对用户操作步骤的分析,正好可以用来组织这些实例。上图所示,正是按操作步骤分别列举对应的实例(业务规则)。
这些实例可以用条目化的方式表达。同时,当规则组合较多时,可以将他们抽取为数据表格。上图中关于”共同贷款人审查“这一步骤的规则,就被抽象成了表格。这一方面让规则更清晰和易于理解;另一方面,将来表格在映射为测试用例时,能自然的做到测试流程和测试数据分离,更易于阅读、维护和扩展。
以上列出的都是功能性的需求和规则。在实际过程中可能还会涉及非功能性需求,如可用性、可靠性、性能、安全性等。非功能性需求大部分体现在系统级别,而实例化需求针对的是单个需求。实例化需求过程中,一般只需要列出与特定功能相关的非功能性需求,如针对某一特定操作的特定安全性和性能要求。
针对业务规则的典型挑战性检验是:
- 相关业务规则考虑全面吗?
- 特殊情况,异常或错误处理包含了吗?
- 是否考虑了不同用户、数据和操作类别?
认真应对以上的挑战,将确保规则的完整性,以及产品交互细节的合理性。
先来一起看一下某测试:
- 首先添加员工信息。
- 执行发放工资的动作。
- 检查工资数值是否正确。
- 检查付款支票号码正确。
这是一个反例。需求必须精确,不能在用户故事开始实现时仍处于模糊的状态。例如,付款支票应该包括哪些信息?这属于领域建模的范畴,应该通过领域模型予以回答。除此之外,我们仍然会提出下面的一些问题,例如:付款支票日期究竟应该使用发薪日期还是当前日期?工资发放的计算规则是否在本需求范围内?付款支票号码的编号策略有没有什么要求?等等。
即使一个看起来很清楚的需求,我们也建议举一两个实际的案例来说明。就像下面这种图片展示的那样:
6. 例子
举例说明是项目需求交流过程中不可或缺的,团队中的人领域背景不同,对同一个事物的理解也可能不尽相同,通过举例说明的方式可以让目标更一致。
功能模块的例子必须具有精确性(不是简单的是或否的答案,使用具体的例子)、真实性(使用真实数据,从客户那儿获取真实的例子)、完整性(使用不同的数据组合去试验,利用其他方式去检验和测试),并易于理解(不用试验所有组合,寻找隐含的概念)。
- Tip1:例子应该关注用户和系统之间的交互,而非关注系统本身的处理流程。因此,例子应该包含前置条件、输入、输出。前置条件指的是场景发生时,未作为输入传递到本系统中,但是已经存在,且对业务产生影响的数据。
- Tip2:当大家用说的方式解释不清的时候,举例子是自然而然的选择。事实上,即使你觉得能说清楚,也应该举例,以免大家的理解有误差。例子应该具体而精确,避免使用范围。例如,不要用某一值“小于10”这一表述,而应该用某一值等于“9”来举例。
- Tip3:在场景特别复杂的情况下,还可以使用流程图来辅助举例。使用什么样的方式不重要,重要的是这种方式能够达到在团队中澄清需求的目的。
- Tip4:如果发现实例太复杂,就把它的复杂度降低,分解成若干个实例。例如,对于“如果数量大于10件,或者重量大于50kg,则收取50元运费”这个规则,可以拆分为“数量大于10件”和“重量大于50kg”两个规则,再来举出数量为20件和重量为60kg两个实例。
- Tip5:在举例说明的过程中极有可能会发现之前未能识别出来的潜在概念。当潜在概念出现时,应当把它加入到领域模型之中。
虽然协作过程中的需求讨论可以建立大家对相关领域的共识,但得到的实例往往包含很多不必要的细节。关键实例是从这些实例中提炼出来的,虽然精简但足以说明业务的实例。并且这些提炼好的实例本身就可以当作交付的验收条件。
- Tip1:摒弃对业务走向没有影响的实例。例如,当输入中的购买者字段是“中学生”“小学生”“大学生”时,如果它们的区别仅在于名称不同,系统对业务的处理完全一样,此时应该只保留其中一个实例作为代表。
- Tip2:提炼实例可以由简入繁。可以先把基本的成功情况提炼出来,再逐步推及到各种异常和失败。关注影响业务规则的实例,关注边界条件的实例。
- Tip3:实例应当有正反两个方面。比如,有一条业务规则是:对于购买重量在10kg以上的订单,才收取6元运费。所举出的实例就应该有正反两个:一个是重量是11kg,收取运费6元;一个是:重量为5kg,收取运费0元。
一个好的需求说明的例子:
一个劣质的需求说明的例子:
根据原则改进的例子:
04 用自动化测试来频繁验证需求
- Tip1:测试是为了验证需求,因此不可以只偏重于测试本身,而忽略了测试和需求之间的联系。过度饱和的数据和测试用例的大爆炸是不可取的。
- Tip2:还是那句,测试是为了验证需求,因此不要在测试代码中引入业务流程或者业务逻辑,不要验证系统是怎么做的,而要验证系统做的事对不对。
- Tip3:自动化测试有很多种优秀的工具,但不要执着于于特定工具,认为工具才是解决问题的王道。工具是为了测试服务的。
在传统的流程中,庞大的需求说明往往跟不上实际开发中的需求变更,导致需求文档在交付之时就已经过时。代码才是唯一能真正信任的。但是,如果通过持续集成对需求说明进行频繁验证,我们就能及时发现需求说明和系统代码之间的差异,及时解决它们,从而保证需求说明和代码是一直同步的。可以像信任代码一样信任需求说明。
频繁验证的依据就是提炼需求产生的实例化需求,它是所有过程实施中都必须要反复进行的工作。需求通过频繁验证与用户进行频繁确认;设计通过实例 化需求来频繁验证设计是否满足用户的需求;开发通过实例化需求频繁验证代码中业务逻辑;测试通过实例化需求来频繁验证交付的功能,并作为最后验收测试的依据。
提炼好功能的需求说明之后,我们就有了实现要达到的清晰目标,并且有了一个精确的方式来衡量何时已经实现了目标。每当系统有所变更,提炼过的需求说明就可以用来检查原有功能是否依然生效。自动化验证带实例的需求说明中,如果必须大量修改需求说明,那么就会失去提炼需求说明带来的价值。
主流自动化可执行需求说明工具中,自动化代码依赖于需求说明,而需求说明并不依赖于自动化代码。
手动测试中,准备上下文环境所花的时间往往是主要瓶颈所在。而在自动化测试中,时间主要花在了寻找测试失败的原因上。传统的自动化测试描述的是一系列相互依赖的步骤,所以出了问题很难定位究竟是什么导致了问题。因此如果我们不去使用一个较大的脚本,转而使用更多较小的、专注的且独立的测试,那么可以让测试更具弹性且可以降低维护成本,可以更快找出问题。
用户界面自动化的3个层次:
- 业务规则层。测试所展示的或所操作的是什么?例如:为购买2本或2本以上书籍的客户提供免费送货服务。
- 用户工作流层。用户如何通过UI使用某个功能,以更高的行为级别应该怎么描述?例如,将2两本书放进购物车,输入地址信息,然后验证配送选项是否包含免费送货服务。
- 技术行为层。操作单个工作流的步骤需要哪些技术性步骤?例如:打开店铺主页,使用testuser与testpassword登陆,跳转到/book页面,点击CSS类是book的第一个图片,等待页面加载结束,点击购买链接等。
需求说明应该以业务规则层来描述。自动化层应该通过组合技术行为上编写的程序块来处理用户工作流层。这样的测试易于理解、编写高校,而且维护成本也相对较低。
1. 活文档系统
维护需求文档,通常是件让人头疼的事情。过于繁琐的需求文档会让人丧失维护的动力。可是,系统的重构和更新又偏偏需要这样一份文档。怎么办呢?所幸的是,实例化需求为我们提供了这样一种省时省力的方式——从自动化测试用例中提取文档!
最成功的团队不会满足于一些频繁验证的可执行的需求说明,他们能确保很好的组织需求说明,让大家很容易找到和获取,并让他们保持一致。活文档是关于系统功能可靠的、权威的信息源。他和代码一样可靠,但是更容易阅读和理解。支持人员可以用它来查明系统在做什么以及这样做的原因。开发可以用它作为开发目标。测试用来测试。分析功能变更请求的影响时,业务分析师可以从他开始着手。还提供了回归测试。
提炼出来的需求实例是对系统做了什么事最有力、最准确、最新鲜的描述。而自动化测试用例又使用了可执行的方式实现了这些需求实例,我们再使用一些工具把自动化测试用例提炼为HTML或是PDF的文本,那么,砰!一份简单易懂的活文档就产生了!水到渠成,简单易懂,永不过时!
如果没有活文档,任何重大的重构都是自寻死路。
- Tip1:活文档和敏捷中的用户故事卡有什么区别吗?好像很类似的样子。当然有所不同。用户故事是用故事的形式描述需求,活文档则是运用实例。另一个不同是,敏捷的故事卡在Sprint结束之后就没有用了,通常不会长久保存,而活文档则是易于保存,同步更新的。
- Tips2:活文档必须组织的井井有条,便于访问。
- Tips3:用户故事作为计划的工具是非常出色的,但是他在组织现有系统功能方面没有太大用处。因此可以以功能区域组织活文档层次结构。当前迭代的需求说明是以用户故事和功能来组织的。
05 操作流程
1. Pre-planning Meeting
参加人
产品、开发、测试代表
目标
选择优先级高的需求进行需求澄清和实例撰写、精炼,获得相对成熟的需求规格说明书,并明确下一迭代的backlog。
内容
产品准备好需求说明和基础实例(描述预期功能的关键实例,作为测试的基础用例),产品、开发、测试代表一起对需求进行澄清。
随后开发和测试一起对实例进行扩展(如边界情况、需重点标识的有问题的地方),再精炼,得到相对成熟的需求规格说明书,并准备好cucumber的验收标准。
基本要求是将下一个迭代的全部需求的需求说明和基础实例准备好,其中优先级高的有若干个可以已准备好cucumber验收标准。
小技巧
反馈调查。是检验一组人员是否对需求说明达成共识的好方法。在讨论过一个故事之后,如果有人对故事提出了一个特殊用例,工作坊的主持人必须请参与者写下他们认为系统应该怎样工作,然后比较全组的回答。如果回答不一致,可以按回答结果分成多个小组,每个小组选出一个人来解释他们的回答。通过讨论可以揭示误解的根源。
“我们能出找出会让这个例子失效的数据?”
备注
- Pre-planning meeting不一定要和迭代绑定,只要能保证每次迭代的需求都已经满足上述基本要求即可。
- 在敏捷开发中,用户故事的拆分是一个难点,因为既要保证故事足够小,又要在一个迭代中做到端到端。对于用户故事来说,除了 Card, 还有更重要的 Conversation 和 Confirmation 。如果用户故事的“撰写”、“拆分”都是由团队中的个人完成的,那么无论是不是采取了故事卡片的形式,无论是否进行了拆分,它其实和传统的需求分析方法没有什么区别。真正有别于传统需求方法的,是我们可以通过一组有序的工作步骤、通过协作,从看似模糊、混乱的业务需求中梳理出清晰的脉络,把它们从需求的重重迷雾中发掘出来,识别、澄清、归类这些细节,然后按照价值大小、成本高低、依赖关系等因素为它们分配优先级,成为可以在开发迭代中持续流动的价值单元,以此指导后续的开发活动。因此实例化需求其实是给出了一个需求澄清活动的更好方式。我们并没有去“拆分”用户故事。我们在“讨论”用户故事。讨论使得用户故事的工作流、业务规则都已经足够详尽。事实上,现在不是用户故事拆不开的问题,而是要多细节,就有多细节,也许我们发现,为了方便管理,有些场景下还可能需要做一些小的归并。无论如何,迭代项怎么划分的自主权已经完全把握在工程人员手中。例如,我们可以选择首先实现一个工作流的主干部分,可以选择首先实现工作流的一个业务步骤,可以选择首先实现几条业务规则,推迟后续几条业务规则,等等。因此实例化需求的输出并非和用户故事一一对应,而是可以先对产品功能进行讨论,形成清晰的认知后,再基于多种因素来安排迭代开发中的用户故事。
- 并不是需求的所有情况都讨论完毕,才能开始迭代,需求只要有了一个完整的例子就能开始迭代。尚未讨论完全的情况可以视作模糊的东西在迭代中做探索。即便是简单的例子,在程序开始开发后也能发现问题。通过完成一些例子反而能够更好辅助后面未讨论完全的场景明晰起来。在项目开始阶段实例化需求工作坊会比较密集,但是这样的讨论每个迭代都应该持续发生,用户故事卡片,是一种占位符(Card),能够持续推动活动进行。
2. Scrum Planning Meeting
参加人
团队全员
内容
产品对本次迭代需求进行讲解和澄清。
产品、开发、测试一起对backlog里故事的需求说明及实例进行审核。
随后开发进行规模评估和任务拆解。
需要指定故事负责人。故事会分配给某个特定的开发,他会坚持到该故事完成,他负责与其他团队进行沟通、有效传递信息而无需所有开发人员都参与审核来确保他们理解故事,在看板上跟踪进度、在每日例会上检查状态和清除障碍)开会并仔细检查所有测试。
3. Scrum过程中
待澄清
针对还未形成cucumber验收标准的需求,开发与测试结对,扩展和精炼实例,并编写cucumber验收标准。
在完成编写后,与产品再次进行确认,三方沟通无误后,该需求可进入待实现队列。
待实现
针对已经相对成熟的需求,开发编写BDD测试代码(TDD也进行),然后进行实现。过程中有发现新的用例,则对需求规格说明和cucumber进行更新。
在开发阶段结束前,所有可执行的需求说明都必须运行通过,且通过开发自测。
待验证
测试对已经通过自动化测试的故事进行手工测试和探索性测试。
待验收
全部测试通过后,团队给产品做一个快速的产品演示,让他验收。
待部署
全部集成后,重新运行整个系统的自动化测试,测试对核心功能做手工测试,无误后,代码可上线进入生产环境。
参考文献:
1.《实例化需求:不可或缺的精益、敏捷需求实践》
2.《实例化需求》
本文由 @扶木桑 原创发布于人人都是产品经理。未经许可,禁止转载
题图来自Unsplash,基于CC0协议
非功能性需求怎么描述
干货