从需求进行测试是解决问题的好方法。但我们只能测试存在的东西,而在需求阶段只有idea存在。如果我们能发现idea的缺陷,我们就可以在idea变成产品代码之前修复它们。难点在于,如何从无法感知的东西中发现缺陷,我们必须想象需求中明确表明的、隐含的或不可避免的问题。
这篇文章描述了我(Wayne Roseberry)在参与的一个项目中的一个需求测试经历,我使用了一些有效且相对简单的技术来拦截在需求的第一次迭代中无法立即感知的问题。这些技巧是:
- 完成需求中描述的所有步骤
- 要求明确需求内容
- 明确“如果这一步失败,会发生什么?”
- 明确“此时用户应该做什么?”
- 明确新问题是否表示新需求
让我们看看这个例子。
待测系统:重复问题上报检测应用程序
我有一个名为DupIQ的项目,它可以检测来自自动化、bug或客户反馈的重复问题报告。它获取传入报告的embedding,然后在向量数据库中搜索先前的问题。如果任何问题超过某个相似性阈值,则传入的报告将标记为该问题的副本,并将报告写入问题数据库。如果没有先前的问题高于相似性阈值,则创建新问题,将嵌入存储在向量数据库中,并将新问题和报告存储在问题数据库中。
从模型和BDD(行为驱动开发)的简单要求
我使用TestCompass创建了一个模型来描述“问题上报”功能的需求。为了让TestCompass对模型进行100%路径遍历,我们可以将案例以Gherkin格式导出需求描述:
场景:报告问题-TC1——>前提:问题已上报——>条件:最高问题相似性>阈值(是)——>处理:将报告链接到上一个问题——>结论:将问题报告写入数据库
场景:报告问题-TC2——>前提:问题已上报——>条件:最高问题相似性>阈值(否)——>处理:写入新问题嵌入和:将报告链接到新问题和:写入新的问题配置文件和:将问题报告写入数据库
由此,我可以启动测试。
需求可以是任何形式,可以是图表、列表、形式表达式,如Jira等票务系统中的Gherkin条目,也可以是大型规范文档。
用简单问题审查需求
大量的需求内容可能令人望而却步,需求通常很长并很复杂,但可能未说明的特征和行为之间的关系。并且,“应用程序会在空中翻转煎饼……”这中需求会使人思维麻木,无法主动思考诸如“如果煎饼不在那里怎么办?如果煎饼还没有煎熟怎么办?如果没有足够的空间进行翻转怎么办?”这样的问题。
一种简化的方法是先提出简单的问题,可以提出的问题不计其数。对于此次测试经历,我使用了一个简短的列表:
解释性问题
“这是怎么发生的?”当一个动作看起来像是通过魔法发生时,这很有用。这些需求指定了应该发生的事情,但没有指明使其成为可能的步骤。这是需求本身的一个缺陷,明确将在进一步的分析中被证明是有用的。
“这指向哪里?”系统中的许多错误都源于存储或发送数据到某处。存储点之间和之后的步骤通常很脆弱,通常需要变更需求以修复设计中的错误。
探索性问题
- “如果故障发生在这里/现在,会发生什么?”如果我们问当给定的步骤、操作或需求失败时会发生什么,错误和新需求往往会变得更加明显。依赖的操作、步骤和要求往往暴露更糟糕的错误,我们通常会发现需要某种新的兜底需求,或者可能需要一种不同的方式来满足该需求,从而避免以这种方式失败。
- “发生这种情况时,用户应该做什么?”这个问题在故障模式下非常有效,因为通常用户必须采取某种行动,或者至少需要知道一些事情。也许用户无能为力,但我们需要他们知道这一点。也许其他人,比如管理员或开发人员,是唯一能够解决这个问题的人。即使没有错误,有时动作的最终结果也需要引导用户朝着好的方向前进。
在完成要求时,提出一组容易记住的问题。不需要太死板,也可以问其他问题。该工具可用作存储设备。
结构化
在我的例子中,需求既表示为视觉模型,也表示为Gherkin的例子。这两者都创建了一个结构,可以更容易地一次一个地查看需求的各个部分,比如步骤。
有时需求不是以循序渐进的格式编写的,有时是列表,有时是简短的用户故事。有时它们的结构不太清晰。“问一个简单的问题”技术仍然适用于任何格式,尽管你可能会发现,格式越不结构化,你就越需要更加努力地了解零部件之间的关系。
如果您在审查需求时遇到困难,请随时创建自己的”结构”。选择任何有助于你思考的结构或格式。与团队中的其他人分享,他们会发现这种事情很有帮助。
提出问题
测试“问题上报”需求时,在回答了上述问题后,我发现了以下错误:
- 需要明确如何确定问题的相似性
- embedding存放在哪里?
- 问题报告和问题概要存储在哪里?
这推动了模型的更新:通过检索上报的问题的embedding,然后对向量数据库进行搜索,以获得与先前问题的余弦距离,从而得到相似度建立相似性,从而建立问题相似性。在创建新问题的情况下,报告问题的嵌入将存储到向量数据库中,然后问题报告和问题简介将存储到问题数据库中。在之前的需求中,系统缺少了整整两个组件。对新需求的审查产生了以下新问题:
- 如果embedding的检索失败怎么办?
- 如果embedding的写入失败怎么办?
- 如果问题报告和/或问题概要的写入失败怎么办?
- 看起来,如果embedding的写入成功但问题概要的写入失败,那么在向量数据库中会存在一个独立的embedding,这是否意味着需要一个新的需求来清理这些embedding?
- 对于embedding检索失败、写入失败、问题报告写入失败、问题概要写入失败等情况,最终用户应该如何处理?
- 是否缺少了一个功能,用于在这些失败状态下进行操作通知?
这些问题帮助需求去考虑、重构如何应对每种故障。
按需求测试本质上是迭代式的,提出的问题会激发对通常会提出新问题的要求的更改,需求往往会在一两次迭代中大幅改进。
下面是以Gherkin格式展示的、经过扩展的新测试用例,详细说明了在可能遇到的各种情况及其相应的错误处理措施。
场景 TC1:报告问题错误处理
前提:已报告一个问题
条件:embedding提取成功(YES)
并且:调用向量数据库成功(YES)
并且:最高相似度问题大于阈值(YES)
并且:写入问题数据库成功(YES)
处理:将新问题链接到先前的问题
并且:将问题报告写入数据库场景 TC2:报告问题错误处理 - 写入问题数据库失败
前提:已报告一个问题
条件:嵌入提取成功(YES)
并且:调用向量数据库成功(YES)
并且:最高相似度问题大于阈值(YES)
并且:写入问题数据库失败(NO)
处理:报告数据库写入错误场景 TC3:报告问题错误处理
前提:已报告一个问题
条件:嵌入提取成功(YES)
并且:调用向量数据库成功(YES)
并且:最高相似度问题不大于阈值(NO)
并且:写入向量数据库成功(YES)
并且:写入问题数据库成功(YES)
处理:写入新问题嵌入
并且:写入新问题配置文件
并且:将问题报告写入数据库场景 TC4:报告问题错误处理 - 未找到相似问题且写入问题数据库失败
前提:已报告一个问题
条件:嵌入提取成功(YES)
并且:调用向量数据库成功(YES)
并且:最高相似度问题不大于阈值(NO)
并且:写入向量数据库成功(YES)
并且:写入问题数据库失败(NO)
并且:从向量数据库删除成功(YES)
处理:数据库恢复到报告前的状态场景 TC5:报告问题错误处理 - 未找到相似问题且删除向量数据库失败
前提:已报告一个问题
条件:嵌入提取成功(YES)
并且:调用向量数据库成功(YES)
并且:最高相似度问题不大于阈值(NO)
并且:写入向量数据库成功(YES)
并且:写入问题数据库失败(NO)
并且:从向量数据库删除失败(NO)
处理:报告孤立的向量
并且:在操作日志中记录错误场景 TC6:报告问题错误处理 - 写入向量数据库失败
前提:已报告一个问题
条件:嵌入提取成功(YES)
并且:调用向量数据库成功(YES)
并且:最高相似度问题不大于阈值(NO)
并且:写入向量数据库失败(NO)
然后:向量存储失败(NO)
并且:在操作日志中记录错误场景 TC7:报告问题错误处理 - 向量数据库连接错误
前提:已报告一个问题
条件:嵌入提取成功(YES)
并且:调用向量数据库失败(NO)
然后:报告向量数据库连接错误
并且:在操作日志中记录错误场景 TC8:报告问题错误处理 - 嵌入提取失败
前提:已报告一个问题
条件:嵌入提取失败(NO)
处理:报告嵌入提取错误
并且:在操作日志中记录错误
结尾
我写这篇文章的目的是展示一种有效且简单的方法,即根据需求来测试应用程序。这个例子虽然很小,但大多数项目都会有一套更加丰富、庞大的需求集。但这并不改变该技术的有效性。你可以通过需求阐述、提出简单的问题(如“如果这个步骤失败了会怎么样?”或“用户接下来应该怎么做?”),然后给需求添加足够的结构,使其更容易提出问题,从而得到很多有用的信息。