Как интегрировать поведение в pytest?

Я создаю приложение Django и сильно полагаюсь на pytest для обнаружения и организации своих модульных и функциональных тестов. Однако я хочу применить Behavior Driven with behave Development для будущих тестов. К сожалению, тестовые функции behave не обнаруживаются автоматически pytest.

Как интегрировать behave и его тесты в pytest обнаружение, выполнение и отчетность?


person Jon    schedule 14.12.2016    source источник
comment
Существует плагин для поведения с pytest: github.com/ribozz/behave-pytest   -  person J_Zar    schedule 07.04.2017
comment
Нет, этот плагин делает обратное.   -  person Cyrille Pontvieux    schedule 12.03.2018


Ответы (2)


Pytest и поведение — это два отдельных средства запуска тестов.

Существует плагин pytest для тестирования поведения, который также использует Gherkin в качестве DSL, но реализация шагов использует синтаксис, отличный от поведения, поэтому я не думаю, что вы можете напрямую запускать шаги, которые вы создали с его помощью.

person jgiralt    schedule 14.12.2016

Следуя примеру документации pytest, вы можете получить такие результаты, как эти:

______________________________________________________________ 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

с файлом функций из учебника по поведению

Чтобы запустить 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}"


ПРИМЕЧАНИЕ:

  1. Требуется правильное декодирование статуса, например, сравнение статуса feature, element или step с Enum в behave.model_core.Status
  2. This snippet would call behave as a subproces instead of its internal API. A proper itegration would consider
    1. subclassing behave.runner:Runner, behave.runner:ModelRunner and trigger from within the same process.
    2. используя средство форматирования поведения внутри repr_failure вместо ручного декодирования вывода json
  3. Вы можете добиться большей или меньшей детализации, ориентируясь на целые функции или конкретные шаги, но этот фрагмент показывает демонстрацию только для сценариев.
  4. Из-за (1) вы не будете собирать данные, например, для целей покрытия...
person pwoolvett    schedule 19.02.2021
comment
фрагмент должен был использоваться в качестве руководства, но я все равно обновил его, указав отсутствующий класс. - person pwoolvett; 02.06.2021