Skip to content

Commit

Permalink
Merge pull request #548 from pytest-dev/step-advanced-usecases
Browse files Browse the repository at this point in the history
Step advanced usecases
  • Loading branch information
youtux authored Aug 14, 2022
2 parents 06499f7 + 9ecf4bf commit c5568af
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 156 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Unreleased
- ``parsers.re`` now does a `fullmatch <https://docs.python.org/3/library/re.html#re.fullmatch>`_ instead of a partial match. This is to make it work just like the other parsers, since they don't ignore non-matching characters at the end of the string. `#539 <https://github.com/pytest-dev/pytest-bdd/pull/539>`_
- Require pytest>=6.2 `#534 <https://github.com/pytest-dev/pytest-bdd/pull/534>`_
- Using modern way to specify hook options to avoid deprecation warnings with pytest >=7.2.
- Add generic ``step`` decorator that will be used for all kind of steps `#548 <https://github.com/pytest-dev/pytest-bdd/pull/548>`_
- Add ``stacklevel`` param to ``given``, ``when``, ``then``, ``step`` decorators. This allows for programmatic step generation `#548 <https://github.com/pytest-dev/pytest-bdd/pull/548>`_

6.0.1
-----
Expand Down
140 changes: 137 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ Install pytest-bdd
pip install pytest-bdd


The minimum required version of pytest is 4.3.


Example
-------

Expand Down Expand Up @@ -870,6 +867,143 @@ You can learn more about `functools.partial <https://docs.python.org/3/library/f
in the Python docs.


Programmatic step generation
----------------------------
Sometimes you have step definitions that would be much easier to automate rather than writing manually over and over again.
This is common, for example, when using libraries like `pytest-factoryboy <https://pytest-factoryboy.readthedocs.io/>`_ that automatically creates fixtures.
Writing step definitions for every model can become a tedious task.

For this reason, pytest-bdd provides a way to generate step definitions automatically.

The trick is to pass the ``stacklevel`` parameter to the ``given``, ``when``, ``then``, ``step`` decorators. This will instruct them to inject the step fixtures in the appropriate module, rather than just injecting them in the caller frame.

Let's look at a concrete example; let's say you have a class ``Wallet`` that has some amount for each currency:

.. code-block:: python
# contents of wallet.py
import dataclass
@dataclass
class Wallet:
verified: bool
amount_eur: int
amount_usd: int
amount_gbp: int
amount_jpy: int
You can use pytest-factoryboy to automatically create model fixtures for this class:

.. code-block:: python
# contents of wallet_factory.py
from wallet import Wallet
import factory
from pytest_factoryboy import register
class WalletFactory(factory.Factory):
class Meta:
model = Wallet
amount_eur = 0
amount_usd = 0
amount_gbp = 0
amount_jpy = 0
register(Wallet) # creates the "wallet" fixture
register(Wallet, "second_wallet") # creates the "second_wallet" fixture
Now we can define a function ``generate_wallet_steps(...)`` that creates the steps for any wallet fixture (in our case, it will be ``wallet`` and ``second_wallet``):

.. code-block:: python
# contents of wallet_steps.py
import re
from dataclasses import fields
import factory
import pytest
from pytest_bdd import given, when, then, scenarios, parsers
def generate_wallet_steps(model_name="wallet", stacklevel=1):
stacklevel += 1
human_name = model_name.replace("_", " ") # "second_wallet" -> "second wallet"
@given(f"I have a {human_name}", target_fixture=model_name, stacklevel=stacklevel)
def _(request):
return request.getfixturevalue(model_name)
# Generate steps for currency fields:
for field in fields(Wallet):
match = re.fullmatch(r"amount_(?P<currency>[a-z]{3})", field.name)
if not match:
continue
currency = match["currency"]
@given(
parsers.parse(f"I have {{value:d}} {currency.upper()} in my {human_name}"),
target_fixture=f"{model_name}__amount_{currency}",
stacklevel=stacklevel,
)
def _(value: int) -> int:
return value
@then(
parsers.parse(f"I should have {{value:d}} {currency.upper()} in my {human_name}"),
stacklevel=stacklevel,
)
def _(value: int, _currency=currency, _model_name=model_name) -> None:
wallet = request.getfixturevalue(_model_name)
assert getattr(wallet, f"amount_{_currency}") == value
# Inject the steps into the current module
generate_wallet_steps("wallet")
generate_wallet_steps("second_wallet")
This last file, ``wallet_steps.py``, now contains all the step definitions for our "wallet" and "second_wallet" fixtures.

We can now define a scenario like this:

.. code-block:: gherkin
# contents of wallet.feature
Feature: A feature
Scenario: Wallet EUR amount stays constant
Given I have 10 EUR in my wallet
And I have a wallet
Then I should have 10 EUR in my wallet
Scenario: Second wallet JPY amount stays constant
Given I have 100 JPY in my second wallet
And I have a second wallet
Then I should have 100 JPY in my second wallet
and finally a test file that puts it all together and run the scenarios:

.. code-block:: python
# contents of test_wallet.py
from pytest_factoryboy import scenarios
from wallet_factory import * # import the registered fixtures "wallet" and "second_wallet"
from wallet_steps import * # import all the step definitions into this test file
scenarios("wallet.feature")
Hooks
-----

Expand Down
5 changes: 3 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import os
import sys
from importlib import metadata

sys.path.insert(0, os.path.abspath(".."))

Expand Down Expand Up @@ -51,9 +52,9 @@
# built documents.
#
# The short X.Y version.
version = pytest_bdd.__version__
version = metadata.version("pytest-factoryboy")
# The full version, including alpha/beta/rc tags.
release = pytest_bdd.__version__
release = version

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
Loading

0 comments on commit c5568af

Please sign in to comment.