一、背景介绍
BDD开发测试用例一定时间后,give\when\then语句会越来越多,这会导致BDD用例描述持续恶化,主要表现在:
- 开发人员的思维和表达方式不同会导致多个语句表达一种行为;
- 没有复用组合语句会导致测试点越多,行为描述也会越多;
本文主要尝试从业内在BDD的应用重点看一下第二个问题是否有更好的解法或者组织形式。
二、可行性分析
两种维度的复用策略没有哪种更加优秀,而是需要考虑面向不同的群体,采取哪种策略的才能更好的发挥BDD的潜能。
2.1 given\when\then的复用
2.1.1 Chorus BDD
Chrous在Step Macro中描述了一种行为复用的逻辑,这种逻辑能让行为和实现继续分层管理,从而不让技术实现来破坏行为描述的完整性,具体的示例如下所示:
#file: login.stepmacro
Step-Macro: I log in as user <user> with password <password>
Given I click the login button
And the login dialog is shown
When I type <user> into the field username
And I type <password> into the field password
And I click the OK button
#file: login.feature
Feature: Log In
Scenario: I can log in using the login form
Given I log in as user Nick with password myPassword
Then I am logged in as the user Nick
#file: accountSummary.feature
Feature: Account Summary
Scenario: Account summary link is shown when logged in
Given I log in as user Nick with password myPassword
Then the account summary link is visible on homepage
2.2 step实现函数的复用
2.2.1 behave
和Chorus Bdd比较而言,behave则提供了另外一种step复用的方式,那就是通过定义Step Macro
来使得step函数实现能进行组合复用,通过调用context.execute_steps()
就能调用不同的step函数。
@when(u'I do the same thing as before with the {color:w} button')
def step_impl(context, color):
context.execute_steps(u'''
When I press the big {color} button
And I duck
'''.format(color=color))
2.2.2 pytest-bdd
实际pytest-bdd本身就支持不同step的调用,这个调用没有用任何技术技巧,而是通过调用函数就可以实现,因为step
装饰器没有对函数做任何限制。
def step(
name: str | StepParser,
type_: Literal["given", "when", "then"] | None = None,
converters: dict[str, Callable] | None = None,
target_fixture: str | None = None,
stacklevel: int = 1,
) -> Callable[[TCallable], TCallable]:
"""Generic step decorator."""
if converters is None:
converters = {}
def decorator(func: TCallable) -> TCallable:
parser = get_parser(name)
context = StepFunctionContext(
type=type_,
step_func=func,
parser=parser,
converters=converters,
target_fixture=target_fixture,
)
...
return func
return decorator
这样是否可行?当然从技术维度讲有实现的办法,但是个人不太建议这么做,因为测试函数本身直接调用函数就已经完全丢失了BDD中B的内容。
另外一个不建议的技术原因是pytest是通过大量fixture组织起来的,如果直接调用fixture函数就会发现会提示无法直接调用,会报如下所示的错误:
Failed: Fixture "fixture_name" called directly. Fixtures are not meant to be called directly
def wrap_function_to_error_out_if_called_directly(
function: FixtureFunction,
fixture_marker: "FixtureFunctionMarker",
) -> FixtureFunction:
"""Wrap the given fixture function so we can raise an error about it being called directly,
instead of used as an argument in a test function."""
message = (
'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
"but are created automatically when test functions request them as parameters.\n"
"See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n"
"https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code."
).format(name=fixture_marker.name or function.__name__)
@functools.wraps(function)
def result(*args, **kwargs):
fail(message, pytrace=False)
# Keep reference to the original function in our own custom attribute so we don't unwrap
# further than this point and lose useful wrappings like @mock.patch (#3774).
result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined]
return cast(FixtureFunction, result)
关于pytest的几个核心类可以看这篇文章。
三、技术挑战
3.1 Gherkin生态支持
这个办法属于非Gherkin标准定义语言,无法兼容生态标准。
这个10年前的discussion中已经有些讨论了,结论是**Gherkin
是一个合作工具而不是一门计算机语言**。因为这个议题是10年前的讨论过程,近10年的DSL及IDE发展是非常快的,10年后还是坚持10年前的讨论结论不一定合适。讨论详情
3.2 IDEA-Pycharm
在专业版的Pycharm
中,Gherkin解释器是内置插件,没有办法修改源码。可以通过Gherkin
语言定义的演进从而使得下游生态适配调整。
3.3 IDEA-Intellij
Intellij
的部分插件是开源的,cucumber插件
,应该可以进行侵入修改。
参考文献
- Chorus BDD stepMacro
- Behave Step Macro: Calling Steps From Other Steps
- Discuss Step Macros in Pytest issues
- Discuss Step Macros in Cucumber issues