编写正确用户故事的工程指南
最初发表在我的博客中:https://sobolevn.me/2019/02/engineering-guide-to-user-stories
敏捷开发人员热衷于编写用户故事。这确实是一个强大的工具。但是,从我的实践来看,很多人的做法都错了。
让我们看一个例子:
As a user
I want to receive issue webhooks from Gitlab
So that I can list all current tasks
看起来像是一个有效的用户故事,不是吗?事实上,这个小故事包含多个问题。如果你找不到至少 8 个错误,那么这篇文章值得你一读。
本文主要分为三个部分:
- 改进默认用户故事格式
- 使用 BDD 重写用户故事,使其可验证
- 将用户故事与测试、源代码和文档相链接
虽然某些部分对于不同类别的读者来说可能看起来更有趣,但对于每个人来说,了解完整的方法很重要。
发现并解决问题
众所周知,我们的所有需求都必须是正确、明确、完整、一致、有序、可验证、可修改和可追溯的,即使它们乍一看不像是需求。
用户故事往往不具备某些给定的特征。我们需要解决这个问题。
使用一致的语言
“接收问题 Webhook” 和 “列出所有当前任务” 之间有什么联系吗?
“任务”和“问题”是一回事吗?它们可能是完全不同的东西,也可能只是措辞不当。我们怎么知道呢?
这就是词汇表的用途!每个项目都应该从定义具体的术语开始,这些术语将构建未来通用的语言。我们首先如何构建这个词汇表?我们会咨询领域专家。遇到一个新术语时,我们会确保所有领域专家都能正确且相似地理解该术语。我们还应该注意,同一个术语在不同的情况和语境下可能会有不同的理解。
假设在我们的案例中,在咨询了领域专家后,我们发现“任务”与“问题”是同一个词。现在我们需要删除这个不正确的术语。
As a user
I want to receive issue webhooks from Gitlab
+++So that I can list all current issues
---So that I can list all current tasks
太好了!用同样的词语来描述同样的实体,能让我们的需求更加清晰一致。
用户不想要你的东西
当我们修改最后一行时,我注意到用户的目标是“列出所有当前问题”。为什么这个可怜的用户想要列出一些问题?这样做有什么意义?没有用户想要这样做。这个要求根本就是错误的。
这体现了需求编写中一个非常重要的问题。我们往往会混淆自身目标和用户目标。虽然我们的目标是取悦用户,但我们应该首先关注用户的需求,让他们的需求比我们的需求更重要。我们应该在需求中明确地表达这一点。
我们怎么知道用户想要什么?同样,我们不知道。我们需要咨询真正的用户或他们的代表。或者,如果我们找不到人问,就自己做一个假设。
As a user
I want to receive issue webhooks from Gitlab
+++So that I can overview and track the issues' progress
---So that I can list all current issues
在收集更多反馈后,我们了解到用户需要了解项目的进展,而不是列出问题。因此,我们需要从第三方服务接收并存储问题信息。
删除技术细节
你见过有人真的只想“接收问题 Webhook”吗?
没人愿意这么做。在这种情况下,我们也会把两种不同的关注点混在一起。
用户目标和实现目标的技术手段之间有着清晰的区分。“接收问题 Webhook”显然是一个实现细节。明天它可能会改为 WebSocket、推送通知等等。而用户的目标不会因此而改变。
As a user
+++I want to have up-to-date information about Gitlab issues
---I want to receive issue webhooks from Gitlab
So that I can overview and track the issues' progress
看到了吗?只留下重要信息,实现细节被剥离。
明确角色
从上下文来看,很明显我们正在使用某种与开发人员相关的工具。我们使用 GitLab 和问题管理工具。因此,不难猜测我们的用户类型会有所不同:初级开发人员、中级开发人员和高级开发人员。或许还有项目经理和其他人员。
所以,我们来谈谈角色定义。所有项目都有不同类型的用户。即使你认为没有明确的类型。这些角色的形成取决于产品的使用方式或目标。这些角色的定义必须与我们定义项目术语的方式相同。
在这个特定的用户故事中,我们讨论的是哪种类型的用户?初级开发人员会像项目经理和架构师一样负责概览和跟踪进度吗?显然不会。
+++As an architect
---As a user
I want to have up-to-date information about Gitlab issues
So that I can overview and track the issues' progress
经过合理的推测,我们可以根据不同的用户角色划分不同的用户故事。这让我们能够对交付的功能以及交付对象进行更精细的控制。
扩展用户故事
这种简洁As a <role or persona>, I want <goal/need> so that <why>
很棒,因为它简洁又强大。它为我们提供了一种完美的沟通方式。然而,以下格式也存在一些我们至少应该了解的缺点。
使用户故事可验证
我们现有的用户故事仍然存在一个问题,那就是它无法验证。
我们如何才能确定这个故事(现在或将来)是否对我们的用户有用?我们无法确定。
这个用户故事和我们的测试之间没有清晰的映射。如果能把用户故事写成测试就太好了……
等等,但这确实有可能!这就是为什么我们有行为驱动开发和gherkin
语言。这也是BDD 最初被创建的gherkin
原因。这意味着我们可以按照可验证的格式重写用户故事。
Feature: Tracking issues' progress
As an architect
I want to have up-to-date information about Gitlab issues
So that I can overview and track the issues' progress
Scenario: new valid issue webhook is received
Given issue webhook is valid
When it is received
Then a new issue is created
现在,这个用户故事已经可以验证了。我们可以直接用它来测试并跟踪其状态。此外,我们现在有了高阶需求和实现细节之间的映射,这将使我们能够了解如何准确地满足这一需求。请注意,我们不会用实现细节取代业务需求,而是对其进行补充。
发现不完整性
我们以前gherkin
写用户故事的时候,就开始为用户故事写场景。我们发现同一个用户故事可能有好几个场景。
让我们看一下我们创建的第一个场景:“收到新的有效问题 Webhook”。等等,但是如果我们收到无效的 Webhook 会发生什么?我们是否还应该保存这个问题?也许我们还需要做一些额外的工作?
让我们查阅Gitlab 的文档作为信息来源,了解在这些情况下可能出现的问题以及该怎么做。
事实证明,我们有两种不同的无效情况需要分别处理。
第一种:GitLab 意外地向我们发送了一些垃圾信息。第二种:我们的身份验证令牌不匹配。
现在我们可以添加两个场景来使这个用户故事更加完整。
Feature: Tracking issues progress
As an architect
I want to have up-to-date information about Gitlab issues
So that I can overview and track the issues' progress
Scenario: new valid issue webhook is received
Given issue webhook is valid
And issue webhook is authenticated
When it is received
Then a new issue is created
Scenario: new invalid issue webhook is received
Given issue webhook is not valid
When it is received
Then no issue is created
Scenario: new valid unauthenticated issue webhook is received
Given issue webhook is valid
And issue webhook is not authenticated
When it is received
Then no issue is created
And webhook data is saved for future investigation
我喜欢这个简单的用户故事现在变得相当复杂。因为它向我们揭示了其内在的复杂性。而且我们可以根据日益增长的复杂性调整开发流程。
用户故事排名
目前,架构师“概览并跟踪问题进展”的重要性尚不明确。它是否比我们现有的其他用户故事更重要?既然它看起来相当复杂,我们是否可以做一些更简单、更重要的事情?
排序和优先级对任何产品都至关重要,我们不能忽视它。即使用户故事是我们编写需求的唯一方式。有很多方法可以确定需求的优先级,但我们建议坚持使用MoSCoW 方法。这个简单的方法基于四个主要类别:must
、、和should
。这意味着我们将在文档的某个位置为项目中的所有用户故事创建一个单独的优先级表。could
won't
再次,我们需要询问用户每个功能的重要性。
在与使用我们产品的不同建筑师进行多次交谈后,我们发现这是绝对的must
:
特征 | 优先事项 |
---|---|
经过身份验证的用户必须能够发送私人消息 | 必须 |
建筑师必须跟踪问题的进展 | 必须 |
应该有关于收到私人消息的通知 | 应该 |
可以支持多个消息提供商 | 可以 |
不支持加密私人消息 | 惯于 |
因此,我们现在可以修改用户故事的名称以将其映射到优先功能:
Feature: Architects must track issues' progress
As an architect
I want to have up-to-date information about Gitlab issues
So that I can overview and track the issues' progress
...
我们甚至可以将它们链接在一起。只需使用排序需求表中的超链接指向包含用户故事的功能文件即可。
这样,我们就可以确保该功能将是第一个开发的功能之一,因为它具有最高优先级。
将所有东西连接在一起
如果没有妥善的管理,你很快就会陷入用户故事、测试、源代码和文档的混乱之中。随着项目的不断发展,你将无法分辨应用程序的哪些部分分别负责哪些业务用例。为了解决这个问题,我们必须将所有内容链接在一起:需求、源代码、测试和文档。我们的目标是最终实现如下效果:
我就用这个python
来说明原理。
我可以将用例定义为您的应用程序可以执行的一组独特的高级操作(它看起来与Clean Architecture的观点非常相似)。
我通常会定义一个名为 的包usecases
,并将所有内容都放进去,这样就可以轻松地一次性忽略所有现有的用例。每个文件都包含一个简单的类(或函数),如下所示:
class CreateNewIssueFromWebhook(object):
"""
Creates new :term:`issue` from the incoming webhook payloads.
.. literalinclude:: path/to/your/bdd/user-story/file
:language: gherkin
.. versionadded:: 0.2.0
"""
def __call__(self, webhook_payload: 'WebhookPayload') -> 'Issue':
# Do something ...
我使用sphinx
指令literalinclude
来包含我们用于测试的同一个文件,以记录领域逻辑。我还使用了词汇表来表明这issue
不仅仅是一个随机的词:它是我们在这个项目中使用的一个特定术语。
这样,我们的测试、代码和文档将尽可能地耦合。
这样我们就不用太操心它们了。我们甚至可以自动化这个过程,检查所有类的文档中是否usecases/
都包含指令。.. literalinclude
您还可以使用此类来测试我们的用户故事。这样,您就可以将需求、测试和实现此用户故事的实际领域逻辑绑定在一起。
工作完成!
结论
本指南将帮助您编写更好的用户故事,关注他们的需求,保持源代码清洁,并尽可能多地重复使用以实现不同(但相似)的目的。
文章来源:https://dev.to/wemake-services/engineering-guide-to-writing- Correct-user-stories-1i7f