多重捕捉
你不必停下来捕捉一个单一的论点。黄瓜会在正则表达式中为每个捕获组传递一个参数,所以您可以从一个步骤中抓取尽可能多的细节。
这是一个例子。
让我们想象我们的银行想要开始提供客户储蓄账户以及他们的常规支票账户。客户可以使用ATM在他们的账户之间转账。以下是这个新功能的一个场景:
我们试着写一个可以处理前两个步骤的步骤定义。除了存入的金额外,我们还需要记录帐户的类型,以便我们知道在哪里存入。 我们可以使用我们以前使用的正则表达式的修改版本来捕获帐户的类型:
我们使用简写字符类 w,用加号修饰来表示任何单词字符,至少一次,有效地捕捉单个单词。然后这个词被传递给我们在第二个参数中命名为accountType的方法。
尝试这个
为情景中的下一步写一个步骤定义,当我从储蓄账户转账$ 500到我的支票账户。
步骤定义应该包含三个参数:
1、正在转账的金额
2、转帐中被扣款的账户类型
3、在转帐中接收信用的帐户类型
通过在步骤定义中编写简单的System.out.println语句来测试它,以将每个参数中捕获的值打印到控制台。
灵活性
Cucumber功能的可读性帮助团队在谈论他们正在构建的系统时学会使用无处不在的语言。这是一个非常重要的好处,因为术语的一致使用有助于减少误解,并使沟通更顺利地在团队中的每个人之间流动。
因此,我们要鼓励我们的特性作者对他们在黄瓜功能中使用的名词和动词保持一致,因为它有助于使团队中的任何人都容易理解的特征。同样,我们也希望特征作者能够尽可能自然地表达自己,这意味着他们可能经常使用略微不同的措词来表示完全相同的东西。这可以; 事实上,这是值得鼓励的。黄瓜的功能都是用语言与商业用户沟通,重要的是我们不要强迫他们听起来像机器人。
为了保持这些特性的可读性和自然性,开发这种技巧是非常有用的,使您的步骤定义具有足够的灵活性,以匹配特性作者可能表达的不同方式。这并不像你想象的那么难。
问号修饰符
当匹配面向业务的黄瓜文本时,你经常要表明你不关心你比赛中的奇怪字符,比如当一个单词可能是单数或复数时:
问号如星号和正号一样修改前面的字符,指定可重复的次数。问号修饰符表示零次或一次 ; 换句话说,它使前面的字符是可选的。在步骤定义中,它对复数特别有用:
通过将一个问号后小号的黄瓜,我们说我们不关心这个词是单数还是复数。所以,这个步骤定义将会匹配前面的两个步骤。
另一个有用的技术是使用非捕获组。
非捕获组
记得在轮换我们介绍了如何可以列出一组可能值的正则表达式,由管道符号分隔的一部分。我们可以使用相同的技术为我们的步骤定义增加灵活性,让功能作者用稍微不同的方式说出同样的东西。我们需要做一点小小的改变,但我们会在一分钟之内做到。
为Web应用程序采取这个非常普通的步骤:
假设有人前来写另一个看起来像这样的步骤:
这两个步骤与读者具有相同的含义,但不幸的是,第一个步骤的定义与第二个步骤的定义没有任何修改。有一个步骤定义来识别这两个短语是有帮助的,因为不管你说访问还是去 - 它们都意味着同样的事情并不重要。我们可以使用替代方法放宽步骤定义来接受这个略有不同的措辞:
请注意,我们必须在替换列表前面添加另一个正则表达式魔术。的:在组的开始,标志着它作为非捕获,这意味着黄瓜不会它作为参数传递给我们的块。
锚
您可能已经注意到,步骤定义片段,黄瓜打印未定义的步骤开始与^和结束$。也许你已经很习惯看到他们,你已经不再注意到他们了。这两个元字符被称为锚,因为它们用于将正则表达式的每一端绑定到匹配的字符串的开始和结尾。
你不必使用它们,我们故意将它们排除在这个例子之外,因为我们想等到我们解释它们做了什么。如果你忽略其中的一个或两个,你会发现最终会有一个更灵活的步骤定义 - 可能太灵活了。作为一个愚蠢的例子,假设我们在起始处添加了^锚,但在我们的银行账户步骤定义的末尾省略了$:
这使得一个特别有创意的特征作者可以写下如下内容:
一般来说,最好尽量保持正则表达式的紧凑性,以避免两步定义相互冲突的机会。这就是为什么Cucumber为未定义的步骤生成的片段总是包含锚。不过,离开主播是一个值得了解的技巧,有时可以派上用场。
返回结果
Cucumber是一个测试工具,它是在一个步骤定义的Java代码中,我们的测试发现一个步骤是否成功了。那么,一个步骤定义如何告诉黄瓜是否通过了?
图2. Cucumber如何执行场景
像大多数其他测试工具一样,Cucumber使用异常来传达测试的失败。由于它一次执行一个场景,Cucumber假定一个步骤已经通过,除非其步骤定义引发异常。如果引发的异常是PendingException,那么该步骤将被标记为挂起 - 所有其他异常都会导致该步骤失败。如果一步通过,黄瓜移动到下一步。图2,黄瓜如何执行场景显示了它的发挥出来。
在黄瓜,结果比简单的通过或失败要复杂一点。已执行的方案可能会以下列任何一种状态结束:
失败
有待
未定义
跳过
通过
这些州旨在帮助您指出您在开发测试时所取得的进展。让我们通过一个自动取款ATM场景的例子来说明我们的意思。
未定义的步骤
当Cucumber无法找到与某个步骤相匹配的步骤定义时,会将该步骤标记为未定义(黄色)并停止该方案。如果他们自己没有匹配的步骤定义,则方案中的其余步骤将被跳过或标记为未定义。 为了向您展示这是如何工作的,我们可以运行ATM提款场景。创建一个名为resources / cash_withdrawal.feature的文件,并将以下内容放入其中:
我们还没有编写任何步骤定义,所以当我们运行这个功能时,我们应该看到所有的步骤都是未定义的:
你应该看到每一步都是黄色的,表示它既没有失败(红色),也没有通过(绿色),而是在两者之间。还要注意,黄瓜已经打印出每个缺少的步骤定义的片段。我们可以使用这些作为实现我们自己的步骤定义的起点。
断言和例外
即使你习惯使用像JUnit这样的测试库,你也许还没有意识到这些库中的断言是通过引发异常来实现的。
你可以通过编写一个运行失败的断言的Java程序来证明这一点:
当你运行它时,你会发现这个程序引发了java.lang.AssertionError类型的异常。
待定步骤
当黄瓜发现一个中间步骤的定义正在实施,它标志着这一步(黄色)。再次,该方案将被停止,其余的步骤将被跳过或标记为未定义。
黄瓜如何知道是否已经实施了一个步骤定义?
当然,你必须通过抛出一个PendingException来告诉它。
当你在一个步骤定义中抛出一个PendingException时,这会告诉Cucumber的运行时,这个步骤已经失败了,但是以一种特殊的方式:步骤定义仍在继续。您可能已经注意到Cucumber为未定义的步骤生成的片段在其中引发PendingException ; 现在你明白了为什么。
让我们回到我们的ATM取款场景,向您展示我们的意思。
创建一个名为step_definitions / Steps.java的文件并粘贴到此步骤定义中:
现在当我们运行mvn clean test时,我们会看到它只是试图执行第一步并将其标记为挂起。其余的都是未定义的:
待处理的状态有点像在20世纪90年代你曾经在整个互联网上看到的那些施工标志。你可以用它作为一个临时的路标给你的队友,你正在处理某些事情。
当我们从外部开发一个新的场景时,我们会倾向于在同一个层面上工作,然后进入下一个场景。现在我们专注于添加步骤定义,所以让我们为每个其他步骤添加步骤,并在每个步骤中添加一个待处理的呼叫:
当我们这样做的时候,我们可以检查是否有任何现有的步骤定义,如果没有,就创建一个抛出PendingException的新定义。当我们拖到下一层并开始实施步骤定义时,待处理场景成为我们要做的工作的待办事项列表。
严格的模式
如果在shell脚本中使用--strict命令行选项./cucumber,那么如果有任何未定义或挂起的步骤,它将返回退出代码1(表示错误)。
这在持续集成构建中非常有用,可以发现意外检查到的任何半成品特征,或者重构了步骤定义,并且某些步骤不再匹配。
失败的步骤
如果由步骤定义执行的代码块引发异常,则Cucumber会将该步骤标记为失败(红色)并停止场景。场景中的其余步骤将被跳过。
实际上,由于以下两个原因之一,步骤定义将失败:
该方案无法完成,因为您的步骤定义代码或被测试的系统中有一个错误,导致它引发错误。如果您使用Cucumber从外部驱动您的开发,您将习惯于在开发过程中随时查看这些故障。每个失败消息都会告诉你下一步你需要做什么。
该步骤定义已经使用断言来检查系统状态,并且检查没有通过。如果有人不小心引入了一个错误,那么通常会在外部循环结束时或者在实现该功能很长一段时间后正确地得到这些错误。
一个断言是检查你的测试,描述你期望得到满足的一些条件。由于断言而导致的失败往往发生在Then步骤中,其工作是检查有关系统状态的事情。这些是你正在适应系统的警钟,如果它开始以意想不到的方式行事,它将会熄灭。无论哪种方式,Cucumber都会向您显示异常消息并在其输出中回溯,因此您需要进行调查。
我们已经完成了这一章,但是我们想先给你一个失败的例子。我们需要开始设计我们的测试和我们的系统之间的界面。让我们从简单的事情开始,设想一个Account类,我们可以用它来为我们的场景中的actor创建一个银行账户。我们可以像这样修改步骤定义:
当我们运行这个时会发生什么?我们实际上并没有Account类,所以在编译过程中会失败。所以,我们在Steps.java里面创建一个Account类。请注意,我们正在这里的步骤文件中定义类。别担心,它不会永远留在这里,但是在我们工作的地方创建它是最方便的。一旦我们清楚了解如何与班级一起工作,那么我们可以重构并将其转移到更加永久的家庭。
现在当我们运行mvn clean test时,我们得到以下失败:
正如你所看到的,我们的第一步定义现在成功了。黄瓜已经抓住了第二步定义引发的PendingException,并将其显示给我们正下方的步骤。在底部的总结中,我们可以看到一步走过,一步失败,一步跳过。
我们刚刚学到的东西
将步骤定义看作一种特殊的方法是有用的。
与常规方法不同,名称必须完全匹配,可以通过与正则表达式匹配的任何步骤调用步骤定义。由于正则表达式可以包含通配符,这意味着您可以灵活地使Gherkin步骤更好,更易读,同时保持Java步骤定义代码清洁且无重复。
步骤定义提供了从小黄瓜场景用户操作的简单语言描述到模拟这些操作的Java代码的映射。
步骤定义是通过使用@Given,@When,@Then或其中一个别名为您的口语在黄瓜上注册的。
步骤定义使用正则表达式来声明他们可以处理的步骤。由于正则表达式可以包含通配符,因此一个步骤定义可以处理几个不同的步骤。 步
骤定义通过提高或不提高例外来将结果传达给Cucumber。
现在你已经看到了Gherkin和Step定义是如何融合在一起的,你已经准备好开始使用Cucumber来测试你自己的应用程序了。为了让您的步骤定义与应用程序交谈,您需要学习如何使用Java自动化库中的一个,本书的食谱部分将介绍其中的许多自动化库。如果你正在测试一个Web应用程序,例如,参见第12章,使用Web应用程序。Web服务是覆盖在第15章,用REST的Web服务的工作。
在这里的基础部分,我们将开始在你的黄瓜知识的骨头上多一些肉。对于我们在上一章中教给你的基本关键字,小黄瓜还有很多,这就是我们接下来要探讨的。
表达情景
背景
一个背景中的特征文件部分允许你指定一组共有的文件中的每个场景的步骤。不必为每个场景反复重复这些步骤,而是将它们移动到Background元素中。这样做有两个好处: 如果你需要改变这些步骤,你只能在一个地方改变它们。这些步骤的重要性消失在背景中,因此,当您阅读每个单独的场景时,您可以关注该场景的独特性和重要性。 为了向您展示我们的意思,让我们来看一个现有的场景,只使用基本的Gherkin Scenario元素,并通过重构它来使用Background来提高其可读性。这是我们的重构开始之前的功能:
你可以看到这里有两个场景,但是如果不仔细阅读,很难看到每个场景到底发生了什么。每种情况下的前三个步骤,虽然有必要澄清场景的情况,但在两种情况下都是完全重复的。这重复是分散注意力,使其难以看到本质的东西每个场景的测试。
让我们把这三个重复的步骤分解成一个背景,像这样:
我们的重构根本没有改变测试的行为:在运行时,后台的步骤在每个场景的开始处执行,就像以前一样。我们所做的是使每个单独的场景更容易阅读。 每个功能文件可以有一个“ 背景”元素,并且必须出现在任何“ 场景”或“ 场景大纲”元素之前。就像所有其他的小黄瓜元素一样,您可以给它起一个名字,在第一步之前,您可以有空间进行多行描述。例如:
使用“ 背景”元素并不总是必要的,但通过从各个场景中删除重复的步骤来提高功能的可读性通常很有用。
以下是使用它的一些技巧:
不要使用Background来设置复杂的状态,除非这个状态是读者实际需要知道的东西。例如,我们在前面的例子中没有提到系统生成的PIN的实际数字,因为这些细节与任何场景都不相关。
保持简短的背景部分。毕竟,你期望用户在阅读你的场景时能真正记住这些东西。如果背景长度超过四行,你能找到一个方法来表达这一行动只需一两步?
使您的背景部分生动。使用丰富多彩的名字,并尝试讲一个故事,因为你的读者可以跟踪故事比他们可以跟踪像用户A,用户B,网站1等沉闷的名字好得多。如果值得一提的话,要让它真正脱颖而出。 保持你的场景简短,并没有太多。如果背景长度超过三或四步,请考虑使用更高级别的步骤或将特征文件分成两部分。您可以使用背景作为功能何时变得太长的良好指示器:如果您要添加的新场景与现有背景不符,请考虑拆分功能。
避免将技术细节(如清除队列,启动后端服务或在后台打开浏览器)。大多数的这些东西会被读者假设,并且有一些方法可以推动这些行动分解为您支持的代码,我们将在本书后面解释,如标记挂钩。 背景对于在每个场景中重复的Given(有时是When)步骤以及将它们移动到一个位置都很有用。这有助于保持您的方案清晰简洁。
重构到背景
重构[19]是改变代码以改进其可读性或设计而不改变其行为的过程。这项技术适用于小黄瓜的功能,就像它对代码库的其他部分一样。随着您对域名的理解在项目过程中不断增长,您需要通过更新功能来反映这一学习。
通常你不会立即看到背景。你可能会开始写一个或两个场景,只有在你写第三个场景时才会注意到一些常见的步骤。当您在多个方案中发现重复执行相同或相似步骤的功能时,请参阅是否可以重构将这些步骤提取到背景中。这样做可能需要一点点勇气,因为有可能会犯一个错误并破坏某些东西的风险,但是这是一个非常安全的重构。一旦你完成了,你最终的功能应该和你开始之前一样,但是更容易阅读。
数据表
有时,场景中的步骤需要描述不容易放在Given,When或Then的单一行上的数据。小黄瓜允许我们把这些细节放在一个台阶下的桌子上。数据表为您提供了一种方法,可以将一个黄瓜步骤延伸到一行以外,以包含更大的数据。
例如,请考虑以下步骤:
无聊!在传统的规范文档中我们不会容忍这种重复的东西,我们也不必在Cucumber规范中容忍它。我们可以将这些步骤一起收集到使用表格来表示数据的单个步骤中:
这更清楚。该表紧接在该步骤后面的行开始,其单元格使用管道字符分隔:| 。你可以使用空格排列管道,使表格看起来整洁,虽然Cucumber不介意你是否做; 它将删除每个单元格中的值,忽略周围的空白。
在前面的表格中,我们使用了表格中每一列的标题,但这只是因为它对于特定的步骤有意义。您可以以不同方式指定数据,例如将标题放在一边:
或者只是指定一个列表:
为了解释如何使用这些不同形状的表格,我们需要稍微深入到步骤定义层。如果你对编写Java步骤定义代码不感兴趣,可以跳过这一点。
在步骤定义中使用数据表
我们将演示如何在步骤定义中使用数据表并快速进行井字游戏。让我们想象一下,我们正在构建一个井字游戏,我们已经开始研究在棋盘上进行移动的基本功能。我们从这样一个场景开始:
我们将向您展示如何从第一步抓取表格,在第二步中操纵表格,最后将场景中预期的电路板与实际电路板进行比较。
运行./cucumber生成步骤定义片段,并将其粘贴到step_definitions / BoardSteps.java中:
请注意,我们将要收到表格的两个步骤定义的片段稍有不同。有一条评论告诉你关于你正在传递的DataTable参数的自动转换,但是现在我们将忽略它(不要担心 - 稍后我们会谈到很多关于自动转换的内容)。该cucumber.api.DataTable是有很多与它的数据交互方法真正富有的对象。我们现在将向您展示一些最有用的。
让我们开始充实这些步骤定义。
将表格转换为列表
引擎盖下,表只是一个清单的名单第字符串 S:名单<名单<字符串>>。通常我们会想用这种原始形式来处理它,所以我们可以用它的原始方法来做到这一点。让我们从表中获取原始数据并将其存储在实例变量板中,当我们想要移动时,我们可以在第二步中操作该实例变量板。
作为一个实验,为第二步定义添加一个实现,只是打印原始板,以便我们看到它的样子:
当你运行./cucumber时,你应该看到打印的二维数组。
请注意,原始表格包含列标题和行标题。
比较表与差异
因此,我们可以从一个失败的测试开始,现在我们将跳过此步骤中的任何操作。因此,删除第二步定义的主体,并在最后一步定义中进行以下实现:
我们在表格中使用了diff方法来描述事物的外观,并将其传递给我们在应用程序中看到的实际板子。当您再次运行mvn clean test时,您应该看到该步骤失败,因为表格不相同:
与预期不同的行将被打印两次,第一个(之前是“ - ”)是预期的,然后是另一个(前面加“ + ”),这是实际返回的。
我们来修复@When步骤以使场景通过。将此实现添加到第二步定义:
运行这个场景,不幸的是你现在会看到一个运行时错误:
发生错误是因为DataTable不可修改。我们将解释为什么Cucumber稍后会以这种方式工作,但现在,我们通过修改第一步定义来制作一个可修改的原始数据副本:
运行场景,现在你应该看到它通过。
这只是对黄瓜数据表的一种尝试。我们建议您阅读文档[20]对cucumber.api.DataTable和玩它自己。
数据表是小黄瓜的一大特色。它们是非常通用的,它们可以帮助您简洁地表达数据,正如您希望在正常的规范文档中一样。使用背景和数据表,您可以做很多事情来减少场景中的噪音和混乱。即使使用这些工具,仍然有时会看到一种模式,其中一种情况看起来和之前的情况看起来很像。这是场景大纲可以提供帮助的地方。
情景大纲
有时,您有几个方案完全遵循相同的步骤模式,只是输入值不同或预期结果不同。例如,假设我们正在测试ATM上的每个固定金额提取按钮:
再一次,这个特性的所有重复使得读起来很无聊。很难看出每个场景的本质,即每笔交易涉及的金额。我们可以使用场景大纲来指定一个步骤,然后通过它们播放多组值。这个场景再次重构为使用场景大纲:
我们使用尖括号(<..>)表示场景大纲中的占位符,我们希望实际值被替换。如果没有示例表,场景大纲本身是无用的 ,该表列出了要替换每个占位符的值行。
每个场景大纲下的特性和任意数量的示例表中 可以包含任意数量的场景大纲元素。在幕后,Cucumber将Examples表中的每一行转换成一个脚本,然后执行它。
使用场景大纲的优点之一是,您可以清楚地看到示例中的空白。在我们的例子中,我们没有测试任何边缘情况,例如当您尝试提取超出您的可用资金时。当你看到所有在一起排列的值时,这变得更加明显。
请记住,尽管在Gherkin中编写它们的语法是相同的,但这些表与本章前面介绍的数据表完全不同。数据表只描述一个数据集,以附加到单个场景的单个步骤。在场景大纲中,Examples表的每一行代表了由Cucumber执行的整个场景。事实上,如果您发现更具可读性,您可能希望使用关键字Scenarios(请注意额外的s)来代替示例。
更大的占位符
很容易想象,只有在步骤中有一段数据的情况下,才可以使用场景大纲占位符。实际上,当Cucumber将场景大纲的示例表编译成可以执行的场景时,它并不关心占位符的位置。所以,你可以从任何一个步骤的文本中尽可能地替代你想要的。
让我们通过测试边缘情况来说明这一点,我们试图从中抽出更多的钱。在这种情况下我们应该做什么?给予用户尽可能多的余额,或只是给他们一个错误信息?我们要求我们的利益相关者澄清,他们很高兴我们向他们显示错误消息。以下是我们如何编写该方案:
这里面的情况还是有很多重复,但是因为Then步骤是如此不同,所以我们不能把它放到场景大纲中。或者我们可以吗?
让我们改变场景大纲,用更抽象的<结果>替换<Received>占位符:
现在我们可以简单地将我们的失败案例添加到该表的底部:
我们可以使用占位符来替换我们在一个步骤中喜欢的任何文本。请注意,占位符在表格中出现的顺序无关紧要。重要的是,列标题与场景大纲中的占位符中的文本相匹配。
乔问:我应该使用多少个例子?
一旦你有了几个例子的场景大纲,想起更多的例子是非常容易的,甚至更容易添加它们。在你知道之前,你有一个巨大的,非常全面的例子的表格和一个问题。
为什么?
在一个任何严重复杂的系统上,你可以很快开始体验到数学家称之为组合爆炸的情况,其中输入和预期输出的不同组合变得难以管理。在试图涵盖每一种可能的情况下,你最终会得到用于执行的Cucumber示例数据的行和行。请记住,这些小行中的每一行表示可能需要几秒钟才能执行的整个场景,并且可以快速开始累加。当你的测试花费更长的时间来运行时,你会减慢你的反馈循环,从而导致整个团队的生产效率下降。
一个很长的桌子也很难读。旨在使您的示例更具说明性或代表性,而不是详尽的。试着坚持Gojko Adzic所说的关键事例。[21]如果你研究你正在测试的代码,你会发现你的例子表中的一些行覆盖了与表中另一行相同的逻辑。您也可能会发现表中的测试用例已经被底层代码的单元测试覆盖了。如果不是,请考虑是否应该这样做。
请记住,可读性是最重要的。如果您的利益相关者通过彻底的测试感到安慰,也许是因为您的软件在安全关键环境中运行,那么通过一切手段将它们放入其中。只要记住,您将永远无法证明没有错误。正如逻辑学家所说,没有证据不是缺席的证明。
虽然这是一个有用的技巧,但是请注意,程序员不惜一切代价减少重复的本能不会在这里占据。[22]如果将一个步骤的文本太多地移动到示例表中,则可能很难读取该场景的流程。记住你的目标是可读性,所以不要太过分,并且要经常让其他人定期阅读并给你反馈来测试你的功能。
示例的多个表格
如果你愿意的话, 黄瓜会乐意处理场景纲要下面的任意数量的示例元素,这意味着你可以将不同类型的示例组合在一起。例如:
像往常一样,您可以选择每个示例表的名称和说明。当你有大量的例子时,把它分成多个表格可以让读者更容易理解。
解释自己
您通常会使用场景大纲和示例表来帮助指定业务规则的实施。请记住包括一个简单的语言描述的基本规则,这些例子应该说明!人们经常忘记这样做,真是惊人。
例如,看看这个功能:
如果你必须实现代码才能使这个特性通过,你能告诉底层的规则是什么吗?
不是很容易。所以,让我们修改这个特性,使其更加明了,就像这样:
通过将这些例子分成两组,给每一个例子一个名称和描述,我们已经解释了规则,同时给出了规则的例子。
太多信息
在你的场景中找到正确的细节或抽象层次是需要一些时间掌握的技巧。许多人没有意识到,不同层次的细节适用于同一系统中的不同场景 - 有时候是同一个特性 - 取决于它们所描述的内容。
举个例子,我们的ATM用户使用他们的PIN进行身份验证的情况如下:
在这种情况下对这个认证过程进行更详细的描述是完全合适的,因为这是我们关注的重点。现在考虑一下我们之前提到的现金回收方案,它有一个不同的重点,但仍然需要验证。在相同的细节水平下表达这种情况下的PIN验证步骤是否有意义?让我们试试看:
那是糟糕的!关于认证的噪音太多了,我们几乎没有注意到这个重要的部分:关于提取现金的部分。这个细节在PIN相关的情况下很有用,但现在只是分散注意力。我们将讨论更多的过于详细或危险势在必行的方案在第6章,使你的黄瓜甜 ; 现在我们希望鼓励您将这些细节提取到单独的步骤定义中,以便我们的场景易于阅读。
提取详细信息
让我们采取三个身份验证步骤,并总结一下他们在单个高级步骤中所做的工作:
从场景中删除这三行,并用这一步替换它们。现在运行./cucumber生成新的高级步骤的步骤定义片段。它应该是这样的:
现在,在步骤定义文件中创建一个名为authenticateWithPIN的方法,它执行任何必要的PIN验证,并将步骤定义修改为如下所示:
您的authenticateWithPIN可能会完成与它正在替换的三个步骤定义完全相同的调用,或者可能有另一种方法使ATM进入已认证状态。无论哪种方式,情景现在更可读:
文档字符串
文档字符串允许您指定大于您可以放在一行上的文本。例如,如果您需要描述电子邮件的确切内容,您可以这样做:
就像一个数据表一样,“”“三重引号之间的整个字符串被附加到它上面的步骤中。”“”开头的缩进并不重要,尽管通常的做法是从封闭步骤中缩进两个空格,我们已经展示了。但是,三重引号内部的缩进是很重要的:假设左边距是从第一个“”“开始的,如果你想在你的字符串中加入缩进,你需要在这个缩进内缩进。
文档字符串打开了在您的步骤中指定数据的各种可能性。例如,我们已经看到,团队使用这些参数在为API编写功能时指定JSON或XML数据的片段。还有如何做到这一点在第15章中,例如用REST的Web服务的工作。在场景中包含这些细节时,您需要保持谨慎。大量的数据很容易造成混乱,使整个场景难以阅读。您也可以轻松地创建脆弱的场景,对系统稍作修改会导致场景失败,因为它的行为与文档字符串中的描述方式略有不同。
好的,我们介绍了可以用来表达业务需求的更多高级功能。在本章中我们要谈论的最后一件事是保持组织。
保持与标签和子文件夹组织
当只有几个特性时,组织起来很容易,但随着测试套件的开始增长,您需要保持整洁,以便文档易于阅读和导航。一个简单的方法是开始使用子文件夹来分类你的特征。尽管如此,这只能给你一个组织轴,所以你也可以使用标签来将标签附加到任何场景中,从而允许你有多种不同的方式来分割你的特征。
子文件夹
这是组织您的功能最简单的方法。不过,您可能会发现自己对如何选择某个类别感到厌倦,例如,您是否按用户类型进行了组织,包括功能/管理员文件夹,功能/ logged_in_users文件夹以及功能/访问者文件夹?或者你是由域实体或其他组织他们吗?
当然,这是你和你的团队做出的决定,但我们可以提供一些建议。使用子文件夹代表用户可能尝试执行的不同高级别任务,我们取得了最大的成功。所以,如果我们正在建立一个内联网报告系统,我们可以像这样组织:
不要太担心第一次让你的文件夹结构正确。做出决定尝试一个结构,重新组织所有现有的功能文件,然后坚持一会儿,因为你添加新的功能。在日历中记下一个笔记,在几个星期内抽出一些时间,并反思新结构是否正常工作。
如果您将功能视为描述系统功能的书,那么子文件夹就像该书中的章节一样。所以,当你讲述你的系统的故事时,你希望读者在扫描目录时看到什么?
Aslak说:功能不是用户故事
很久以前,黄瓜开始作为一种工具被称为RSpec故事亚军。在那些日子里,简单的语言测试使用了一个.story的扩展。当我创建黄瓜,我做了一个故意的决定,命名文件的功能,而不是故事。我为什么这样做?
用户故事是规划的好工具。每个故事都包含一些功能,您可以优先考虑,构建,测试和发布。一旦一个故事已经发布,我们不希望它在代码中留下痕迹。我们使用重构来清理设计,以便代码吸收用户故事指定的新行为,使其看起来好像这种行为一直存在。
我们希望同样的事情发生在我们的黄瓜功能。这些特性应该描述系统今天的行为,但是他们不需要记录它是如何构建的; 这是一个版本控制系统!
我们已经看到了其功能目录如下所示的团队:
我们强烈建议您不要这样做。你最终会得到一些碎片化的功能,这些功能只能作为系统的文档。一个用户故事可能会映射到一个功能,但另一个用户故事可能会导致您去添加或修改现有功能中的某些场景 - 例如,如果故事改变了用户必须验证的方式。从每个用户故事到每个特征总是存在一对一的映射是不太可能的,所以不要试图强制它。如果您需要为方案保留故事标识符,请改为使用标签。
标签
如果子文件夹是功能书中的章节,那么标签就是您想要轻松找到的放在页面上的便签。您可以通过在Scenario关键字之前的行上添加以@字符为前缀的单词来标记脚本,如下所示:
实际上,您可以将多个标签附加到相同的场景中,用空格分隔:
如果要一次性标记特性中的所有场景,只需在顶部标记Feature元素,所有场景都将继承该标记。您仍然可以标记各个场景。
在前面的示例中,名为“ 生成通宵小部件”报告的方案将包含三个标签:@nightly,@slow和@widgets,而名为“ 生成通宵托管”报告的方案将包含@nightly,@slow和@doofers标签。您也可以标记场景大纲元素和它们下面的单个示例表。
标记方案有三个主要原因:
1、文档:您想要使用标签将标签附加到特定场景,例如使用项目管理工具中的标识标记它们。
2、过滤:Cucumber允许您使用标签作为过滤器来选择要运行或报告的特定场景。如果某个标签出现过多次,您甚至可能让黄瓜不能通过测试。
3、钩子:每当具有特定标签的场景即将开始或刚完成时,运行一段代码。
我们将介绍在后面钩标记鱼钩,我们将介绍如何基于在标记过滤筛选与标记表达式。如果你不能等到那时,下面是一个简单的例子,如何运行Cucumber,选择带有特定标签的场景:
$ ./cucumber --tags @nightly
这将只选择和运行标签为@nightly的场景。
我们刚刚学到的东西恭喜你,你已经从小黄瓜学校毕业了!让我们回顾一下我们在本章中学到的东西。
在编写小黄瓜功能时,可读性应该是您的头号目标。在编写场景时,总是尽量和利益相关者坐在一起,或者至少在你写完场景之后再把它们反馈给他们。在你的场景中继续微调语言,使其更具可读性。 使用“ 背景”来分解功能中的重复步骤,并帮助讲述故事。
重复场景可以折叠成场景大纲。
可以使用多行字符串或数据表来扩展步骤。
您可以将功能组织到子文件夹中,如书中的章节。
标签允许您标记场景和功能,以便选择要运行或报告的特定设置。
你有没有注意到我们正在阅读本书的这部分内容?我们从一个概述开始,然后看了一下小黄瓜最有用的部分,然后在步骤定义上做了同样的事情。现在您已经了解了小黄瓜的更多高级功能,您几乎已经准备好深入一步定义代码。在本书的下一部分,这正是我们要做的,一个有效的例子,将给你一个机会来练习你学到的一切,等等。首先,我们将退后一步,检查一下您和您的团队在开始使用Cucumber时遇到的一些常见问题以及如何处理这些问题。 尝试这个 查看您为系统编写的场景,并查看是否可以找到使用“ 背景”,“ 场景大纲 ”或数据表的机会。重构该功能以使用新的关键字,并将该功能的新版本与旧版本进行比较。你认为哪一个更具可读性?展示给团队中的其他人; 那人觉得呢?