如何将行为集成到pytest中?
How to integrate behave into pytest?
我创建了一个 Django 应用程序并严重依赖 pytest
来发现和组织
我的单元和功能测试。但是,我想将行为驱动与 behave
开发应用到未来的测试中。不幸的是,behave
测试功能不会被 pytest
自动检测到。
如何将 behave
及其测试集成到 pytest
发现、执行和报告中?
Pytest 和 behave 是两个独立的测试 运行ners。
有一个用于行为测试的 pytest plugin,它也使用 Gherkin 作为 DSL,但是步骤的实现使用了与 behave 不同的语法,所以我认为你不能直接 运行 你用它创建的步骤。
跟随 pytest docs example,您可以获得如下输出:
______________________________________________________________ Feature: Fight or flight - Scenario: Stronger opponent ______________________________________________________________
Feature: Fight or flight
Scenario: Stronger opponent
Step [OK]: the ninja has a third level black-belt
Step [ERR]: attacked by Chuck Norris
Traceback (most recent call last):
File ".venv/lib/python3.6/site-packages/behave/model.py", line 1329, in run
match.run(runner.context)
File ".venv/lib/python3.6/site-packages/behave/matchers.py", line 98, in run
self.func(context, *args, **kwargs)
File "tests/bdd/steps/tutorial.py", line 23, in step_impl4
raise NotImplementedError('STEP: When attacked by Chuck Norris')
NotImplementedError: STEP: When attacked by Chuck Norris
Step [NOT REACHED]: the ninja should run for his life
使用来自 behaves tutorial
的功能文件
为了使 pytest 运行 正常运行,您可以在 conftest.py
中使用以下代码片段:
# content of conftest.py
import pytest
class BehaveException(Exception):
"""Custom exception for error reporting."""
def pytest_collect_file(parent, path):
"""Allow .feature files to be parsed for bdd."""
if path.ext == ".feature":
return BehaveFile.from_parent(parent, fspath=path)
class BehaveFile(pytest.File):
def collect(self):
from behave.parser import parse_file
feature = parse_file(self.fspath)
for scenario in feature.walk_scenarios(with_outlines=True):
yield BehaveFeature.from_parent(
self,
name=scenario.name,
feature=feature,
scenario=scenario,
)
class BehaveFeature(pytest.Item):
def __init__(self, name, parent, feature, scenario):
super().__init__(name, parent)
self._feature = feature
self._scenario = scenario
def runtest(self):
import subprocess as sp
from shlex import split
feature_name = self._feature.filename
cmd = split(f"""behave tests/bdd/
--format json
--no-summary
--include {feature_name}
-n "{self._scenario.name}"
""")
try:
proc = sp.run(cmd, stdout=sp.PIPE)
if not proc.returncode:
return
except Exception as exc:
raise BehaveException(self, f"exc={exc}, feature={feature_name}")
stdout = proc.stdout.decode("utf8")
raise BehaveException(self, stdout)
def repr_failure(self, excinfo):
"""Called when self.runtest() raises an exception."""
import json
if isinstance(excinfo.value, BehaveException):
feature = excinfo.value.args[0]._feature
results = excinfo.value.args[1]
data = json.loads(results)
summary = ""
for feature in data:
if feature['status'] != "failed":
continue
summary += f"\nFeature: {feature['name']}"
for element in feature["elements"]:
if element['status'] != "failed":
continue
summary += f"\n {element['type'].title()}: {element['name']}"
for step in element["steps"]:
try:
result = step['result']
except KeyError:
summary += f"\n Step [NOT REACHED]: {step['name']}"
continue
status = result['status']
if status != "failed":
summary += f"\n Step [OK]: {step['name']}"
else:
summary += f"\n Step [ERR]: {step['name']}"
summary += "\n " + "\n ".join(result['error_message'])
return summary
def reportinfo(self):
return self.fspath, 0, f"Feature: {self._feature.name} - Scenario: {self._scenario.name}"
注意:
- 需要正确的状态解码,例如将
feature
、element
或step
状态与behave.model_core.Status
[=39=中的Enum
进行比较]
- 此代码段将调用
behave
作为子过程而不是其内部 API。适当的整合会考虑
- 从同一进程中继承
behave.runner:Runner
、behave.runner:ModelRunner
和触发器。
- 在 repr_failure 中使用 behave 格式化程序,而不是手动解码 json 输出
- 您可以通过定位整个功能或特定步骤来实现更多/更少的粒度,但此代码段仅显示场景演示
- 由于 (1),您不会收集数据,例如用于报道目的...
我创建了一个 Django 应用程序并严重依赖 pytest
来发现和组织
我的单元和功能测试。但是,我想将行为驱动与 behave
开发应用到未来的测试中。不幸的是,behave
测试功能不会被 pytest
自动检测到。
如何将 behave
及其测试集成到 pytest
发现、执行和报告中?
Pytest 和 behave 是两个独立的测试 运行ners。
有一个用于行为测试的 pytest plugin,它也使用 Gherkin 作为 DSL,但是步骤的实现使用了与 behave 不同的语法,所以我认为你不能直接 运行 你用它创建的步骤。
跟随 pytest docs example,您可以获得如下输出:
______________________________________________________________ Feature: Fight or flight - Scenario: Stronger opponent ______________________________________________________________
Feature: Fight or flight
Scenario: Stronger opponent
Step [OK]: the ninja has a third level black-belt
Step [ERR]: attacked by Chuck Norris
Traceback (most recent call last):
File ".venv/lib/python3.6/site-packages/behave/model.py", line 1329, in run
match.run(runner.context)
File ".venv/lib/python3.6/site-packages/behave/matchers.py", line 98, in run
self.func(context, *args, **kwargs)
File "tests/bdd/steps/tutorial.py", line 23, in step_impl4
raise NotImplementedError('STEP: When attacked by Chuck Norris')
NotImplementedError: STEP: When attacked by Chuck Norris
Step [NOT REACHED]: the ninja should run for his life
使用来自 behaves tutorial
的功能文件为了使 pytest 运行 正常运行,您可以在 conftest.py
中使用以下代码片段:
# content of conftest.py
import pytest
class BehaveException(Exception):
"""Custom exception for error reporting."""
def pytest_collect_file(parent, path):
"""Allow .feature files to be parsed for bdd."""
if path.ext == ".feature":
return BehaveFile.from_parent(parent, fspath=path)
class BehaveFile(pytest.File):
def collect(self):
from behave.parser import parse_file
feature = parse_file(self.fspath)
for scenario in feature.walk_scenarios(with_outlines=True):
yield BehaveFeature.from_parent(
self,
name=scenario.name,
feature=feature,
scenario=scenario,
)
class BehaveFeature(pytest.Item):
def __init__(self, name, parent, feature, scenario):
super().__init__(name, parent)
self._feature = feature
self._scenario = scenario
def runtest(self):
import subprocess as sp
from shlex import split
feature_name = self._feature.filename
cmd = split(f"""behave tests/bdd/
--format json
--no-summary
--include {feature_name}
-n "{self._scenario.name}"
""")
try:
proc = sp.run(cmd, stdout=sp.PIPE)
if not proc.returncode:
return
except Exception as exc:
raise BehaveException(self, f"exc={exc}, feature={feature_name}")
stdout = proc.stdout.decode("utf8")
raise BehaveException(self, stdout)
def repr_failure(self, excinfo):
"""Called when self.runtest() raises an exception."""
import json
if isinstance(excinfo.value, BehaveException):
feature = excinfo.value.args[0]._feature
results = excinfo.value.args[1]
data = json.loads(results)
summary = ""
for feature in data:
if feature['status'] != "failed":
continue
summary += f"\nFeature: {feature['name']}"
for element in feature["elements"]:
if element['status'] != "failed":
continue
summary += f"\n {element['type'].title()}: {element['name']}"
for step in element["steps"]:
try:
result = step['result']
except KeyError:
summary += f"\n Step [NOT REACHED]: {step['name']}"
continue
status = result['status']
if status != "failed":
summary += f"\n Step [OK]: {step['name']}"
else:
summary += f"\n Step [ERR]: {step['name']}"
summary += "\n " + "\n ".join(result['error_message'])
return summary
def reportinfo(self):
return self.fspath, 0, f"Feature: {self._feature.name} - Scenario: {self._scenario.name}"
注意:
- 需要正确的状态解码,例如将
feature
、element
或step
状态与behave.model_core.Status
[=39=中的Enum
进行比较] - 此代码段将调用
behave
作为子过程而不是其内部 API。适当的整合会考虑- 从同一进程中继承
behave.runner:Runner
、behave.runner:ModelRunner
和触发器。 - 在 repr_failure 中使用 behave 格式化程序,而不是手动解码 json 输出
- 从同一进程中继承
- 您可以通过定位整个功能或特定步骤来实现更多/更少的粒度,但此代码段仅显示场景演示
- 由于 (1),您不会收集数据,例如用于报道目的...