SpecFlow/Cucumber/Gherkin - 在场景大纲中使用表格

18

希望我能够清晰地解释我的问题,让其他人能够理解。以下是两个假设情景,希望您能想象一下:

Scenario: Filter sweets by king size and nut content
Given I am on the "Sweet/List" Page
When I filter sweets by 
    | Field               | Value  |
    | Filter.KingSize     | True   |
    | Filter.ContainsNuts | False  |
Then I should see :
    | Value            |
    | Yorkie King Size |
    | Mars King Size   |

Scenario: Filter sweets by make
Given I am on the "Sweet/List" Page
When I filter sweets by 
    | Field        | Value  |
    | Filter.Make  | Haribo |
Then I should see :
    | Value   |
    | Starmix |

这些场景很有用,因为我可以添加任意多个Field/Value和Then Value的When行,而不改变关联编译测试步骤。然而,将场景复制/粘贴到不同的过滤器测试中将变得重复并占用大量代码 - 这是我想避免的。理想情况下,我希望创建一个场景大纲,并保持上面测试的动态性,但是当我尝试这样做时,我遇到了一个问题:定义示例表时我不能根据需要添加新行,因为那将是一个新的测试实例,目前我的代码如下:

Scenario Outline: Filter Sweets 
Given I am on the <page> Page
When I filter chocolates by 
    | Field    | Value   |
    | <filter> | <value> |
Then I should see :
    | Output   |
    | <output> |
Examples:
    | page       | filter      | value  | output  |
    | Sweet/List | Filter.Make | Haribo | Starmix |

我有一个问题,在使用场景大纲时,我无法动态地添加筛选器和预期数据的行,是否有人知道解决方法?我应该从不同的角度来处理这个问题吗?

一种解决方法可能是:

Then I should see :
    | Output |
    | <x>    |
    | <y>    |
    | <z>    |
    Examples:
    | x | y | z |

但这并不是很动态... 希望有更好的解决方案?:)

2个回答

36
我认为,使用 SpecFlow、Gherkin 和开箱即用的 Cucumber 并不能满足你所要求的功能。虽然我不能代表作者说话,但我猜测这是有意为之的,因为这与编写和实现规范的整体“流程”相违背。规范的目的是使非程序员能够阅读,并给程序员提供实现匹配规范的代码指南,用于集成测试,并在重构时提供一定的灵活性。
我认为这是其中一个情况,你感到痛苦是因为有问题,但可能不是你想象中的那个问题。你说:
“但是,将场景复制/粘贴到不同的过滤器测试中将变得重复,并占用大量代码——我希望避免这种情况。”
首先,我不同意你用书面语解释自己会变得“重复”,至少不会比使用特定的单词如“the, apple, car”等更加重复。问题在于:这些词是否适当地解释了你正在做的事情?如果适当地解释了你的情况需要你写出多个场景,那就只需要这么做。交流需要用到单词,有时候需要用相同的单词。
实际上,你所谓的“重复”是使用 Gherkin 和类似 Cucumber 或 SpecFlow 工具的好处之一。如果你能够一遍又一遍地使用同一句话,那就意味着你不必一遍又一遍地编写测试代码。
其次,你确定你正在为正确的事物编写规范吗?我之所以问这个问题,是因为如果场景数量过多,到了人类无法理解你的写作时,可能你的规范没有针对正确的事物。

这里举一个可能的例子,就是在测试筛选和分页时应该如何处理。是的,您希望您的规格覆盖完整的功能,并且您的网站将在与筛选相同的页面上具有分页功能,但代价是什么?需要经验和实践才能知道放弃所谓的“理想”、进行模拟和完全集成测试会产生更好的结果。

第三点,不要认为规格是为了涵盖每种可能的场景而设计的完美覆盖。场景实际上是状态的快照,这意味着有些功能可以涵盖无限大的场景集,这是不可能的。那么你该怎么办呢?编写尽可能好地讲述故事的特征。甚至让故事驱动开发。然而,不能转化为您的规格或其他情况的细节最好留给纯TDD,在规格之外完成。

在您的示例中,似乎您正在讲述一个允许用户创建针对糖果和糖果的动态搜索的站点的故事。他们输入可能的大量搜索条件之一,单击按钮,然后获得结果。只需坚持这一点,编写足够的规格来实现故事即可。如果您对覆盖范围不满意,请使用更多的规格或单元测试来清除它。


总之,这只是我的想法,希望能有所帮助。


2
Darren,非常感谢您的评论,我对BDD还很陌生,您的建议非常有帮助,尤其是第三部分。我已经标记了您的答案,但是由于Patrick提供了原始问题的技术解决方案,所以我将解决方案授予了他。再次感谢。 - MattStacey
SpecFlow(至少v2版本)支持参数驱动表格,支持场景大纲示例... 示例在此处:https://dev59.com/c2Ij5IYBdhLWcg3wUjzG#37349163 - Brett Veenstra

8

从技术上讲,我认为您可以尝试在步骤定义中调用步骤:

从步骤定义中调用步骤

例如,我认为您可以重写

Then I should see :
| Output   |
| <output> |

成为像自定义步骤一样的内容
I should have output that contains <output>

输出是一个逗号分隔的期望值列表。在自定义步骤中,您可以将逗号分隔的列表拆分为数组,并对其进行迭代,调用

Then "I should see #{iterated_value}"

您可以使用类似的技术来传递过滤器列表和过滤器值。您针对特大号测试的示例行可能如下所示:
| page       | filter                               | value       | output                           |
| Sweet/List | Filter.KingSize, Filter.ContainsNuts | True, False | Yorkie King Size, Mars King Size |

Or maybe

| page       |                              filter-value-pairs | output                           |
| Sweet/List | Filter.KingSize:True, Filter.ContainsNuts:False | Yorkie King Size, Mars King Size |

话虽如此,也许你应该认真考虑达伦的话。我并不确定这种方法是否有助于实现让非开发人员能够阅读的场景的最终目标。


嗨Patrick,感谢您的评论。我已将此标记为正确答案,因为它提供了解决我的原始问题的方法,尽管我同意您和Darren,也许这会违反具有非开发人员可理解的场景的最终目标。 - MattStacey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接