【问题标题】:Reusing gherkin steps with cucumber用黄瓜重复使用小黄瓜步骤
【发布时间】:2018-08-06 03:38:52
【问题描述】:

据我了解,基于此Given When Then wiki pageGiven 步骤与应用程序交互以设置前置条件状态,When 步骤与应用程序交互以尝试设置正在测试的所需新状态,以及 @987654328 @ 语句读取应用程序的状态而不修改它。

When 步骤作为Given 步骤重复用于后续状态是否是个好主意?

例如,在一个简单的购物车应用程序中,我可能会这样写:

Given the user is interested in some item
When the user adds the item to their cart
Then the cart will include the item

Given the user adds the item to their cart
When the user checks out
Then the user will see a summary of their purchase including the item

Given the user checks out
When the user cancels an item
Then the item should be canceled
And the user should be refunded

这里有一个类似的问题reusing the user's previous interaction in a wizard,但它似乎与Uncle Bob's finite-state-machine interpretation 不同意,因为答案建议使步骤不那么严格,而鲍勃叔叔暗示这些步骤应该足够严格以使人理解状态转换图出来。我完全接受删除所有用户界面行话并只关注业务术语的建议,但在逻辑上连接业务术语之间似乎确实存在差异,就像我在这里尝试做的那样,只是做出逻辑上不可连接的步骤仅通过“看不见”的胶水代码连接。

【问题讨论】:

标签: cucumber cucumber-jvm gherkin


【解决方案1】:

所以这是一个有点奇怪的领域,因为它是你可以但你真的不应该做的事情之一。让我解释一下。

正如你所说:

给定步骤与应用交互以设置前置条件状态

当步骤与应​​用交互以尝试设置正在测试的所需新状态时

让我们在查看您的示例时牢记这一点

When the user adds the item to their cart

这一步其实还不错,缺少实现语言,描述了用户行为。 10/10

Given the user adds the item to their cart

这一步虽然在语法上有效,但违反了我们设置的关于 GivenWhen 步骤的规则。我们已经建立了When 用于交互,Given 用于前置条件。然而,这个Given 描述的是正在采取的行动,而不是状态或先决条件。更好的写法可能是:

Given the user has an item in their cart

这指定了一个前提条件而不是一个动作,所以它是正确的小黄瓜。

所以现在您可能会想“但是代码重用呢!?!?!?”,这就是流和库派上用场的地方。如果您发现自己经常重用代码,请将其移动到一个库中,您可以调用该库以在您的 stepdefs 中引用该操作,这样您就可以将您的功能文件保存在适当的小黄瓜和您的 stepdefs 中,尽可能少地重用代码

【讨论】:

  • 关于语言的观点很好理解。时态不正确,不适合作为过去时的前提条件。但是,我也关心我称之为“测试崩溃”。在我的示例中,由于我在行为之间构建了依赖关系树,我希望 cucumber 的幼稚实现实际上执行了三次“添加项目”。更好的实现将只执行最后一个行为,并认识到所有其他行为都是在最后一个行为期间执行的,因为它们是前提条件。
  • 但这就是本末倒置。 Cucumber(和其他类似的框架)首先利用预先存在的小黄瓜,然后是测试工具。从测试的角度来看,它可能不会在 100% 的时间里 100% 有意义。但关键是要通过测试来涵盖验收标准。虽然作为代码,步骤可能相同,但作为验收标准,它们是不同的,应该被视为独立的。归根结底,@alayor 所说的是有效的,这是灵活的。您可以通过有利于代码重用的方式来执行此操作,但这不会改变您正在修改 AC 以进行测试的事实。
  • 依赖树,我的意思是对于每一个行为,将系统置于前置条件状态的过程实际上会行使其他行为。例如,要取消一个项目,您必须已结帐,而要结帐,您必须已添加一个项目。这些是状态转换/行为依赖。我已经对我感兴趣的离散行为进行了建模,但这些行为存在于业务流程中,其中的步骤只能通过首先执行之前的相关步骤来实现。
  • 虽然我不一定同意这种方法,因为它通过限制可以使用的位置将您的步骤与功能结合起来。我不明白为什么这仍然不能用库和上下文变量来完成。
【解决方案2】:

你说得对,Gherkin 场景描述了在被测应用程序中安排、执行和断言的步骤。

如果你有足够的 Gherkin 语句,Bob 叔叔的有限状态机可以用 Gherkin 来描述。不过,重要的是要记住,Gherkin 中的每个场景都是从 initial 状态开始的,而不是从功能文件中位于其上方的场景结束时的状态开始的。

换句话说,每个场景都必须独立存在。

测试步骤的潜在重复不如描述完整的场景重要。原因之一是测试运行者可能会选择性地运行场景,如果未执行前驱场景,这将导致测试失败。其他测试运行器可能会并行运行场景,如果允许这种依赖关系,也会造成严重破坏。

在多个场景中重复给定步骤是完全可以接受的,也是常见的做法。

你的例子可以更恰当地表述为:

Given the user is interested in some item
When the user adds the item to their cart
Then the cart will include the item

Given the user has added an item to their cart
When the user checks out
Then the user will see a summary of their purchase including the item

Given the user purchases an item
When the user cancels the item
Then the item should be canceled

第三条 Given 语句可以在其定义中包含让用户将商品放入购物车并结帐的步骤,尽管它也可以简单地从头开始创建发票。这就是 Gherkin 的美妙之处:无论如何实现前提条件,验证是该操作导致了预期的结果。

【讨论】:

  • 我正在考虑接受这个作为答案,因为您已经专注于场景。当时我并没有意识到这一点,因为我专注于如何在测试期间管理应用程序状态的问题,但三年后我想你可能已经一针见血了。
【解决方案3】:

我认为您不应该重复使用 When 步骤作为 Givens。相反,您应该基于 When 步骤与之交互的功能来创建您的 Givens。一般来说,每个 When 步骤都有可能在其功能上构建多个 Given 步骤。

在某些情况下,您会在每个步骤中执行完全相同的操作。例如登录

When "I login" do
  visit login_path
  fill_in id
  fill_in password
  ...
end

Given "I have logged in" do
  visit login_path
  fill_in
  ...
end

为了使这些步骤顺利进行,让我们引入一个辅助方法

module LoginStepHelper
  def login(as: )
     visit ...
  end
end
World LoginStepHelper

When "I login" do
  login as: @i
end

Given "I have logged in" do
  login as: @i
end

现在我们几乎可以免费获得 Given 更好的语法的好处,我们可以在此基础上进行其他有用的步骤

例如

Given Fred is logged in
Given I am logged in as an admin
...

在大多数情况下,您有机会为您的 Given 做一些不同的事情。假设我们要注册一个用户。对于何时我们将通过某种 UI 与表单进行交互。但是对于 Given 我们可以绕过 UI。在我们的实现中,我们会有

When I register
  visit registration_path
  fill_in ...
  ...
  submit
end

虽然我们的Given可能是

Given I am registered
  register as: @i
end

module RegistrationStepHelper
  def register(as:)
    CreateRegistration.call( ...
  end
end

我们的 Given 有几种创建注册的方法

  1. 使用适当的参数哈希直接调用用于创建注册的服务
  2. 调用工厂/夹具创建者直接将注册记录写入数据库。

在这两个中,第一个要好得多。

现在您的语法越来越好,运行时成本也大大降低。当您想要进行涉及大量现有功能的复杂交互时,这一点变得尤为重要,例如注册客户在电子商务网站上进行重新订购。

【讨论】:

    【解决方案4】:

    如果您使用的是 Javascript,我创建了一个名为 reuse-cucumber-scenarios 的包,用于调用场景:

    Given the scenario "@scenario_tag"
    

    .

    Given the scenario "@scenario_tag" with parameters
    """
    {
      "1": ["step1_param1", "step1_param2"],
      "2": ["step2_param1", "step2_param2", "step2_param3"],
      "3": ["step3_param1", "step3_param2", "step3_param3"],
    }
    """
    

    或创建小黄瓜变量...

    Given the variable "$variable_name" is equal to
    """
    #JSON object
    """
    

    或创建场景函数并通过执行来调用它们...

    Given the scenario "@$scenario_function_tag" where variable "$variable_name" is "value_to_replace"
    

    还有更多...

    【讨论】:

      猜你喜欢
      • 2017-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多