Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step advanced usecases #548

Merged
merged 24 commits into from
Aug 14, 2022
Merged

Step advanced usecases #548

merged 24 commits into from
Aug 14, 2022

Conversation

youtux
Copy link
Contributor

@youtux youtux commented Jul 29, 2022

This PR allows for more advanced use cases of the library.

Add generic @step(...) decorator

With this decorator, users can define a step function that will work for Given, When and Then steps:

from pytest_bdd import step, scenarios

scenarios("step.feature")

@step("foo")
def _():
    return foo
Feature: step(...) decorator
  Scenario: Defining steps using @step(...)
    Given foo
    When foo
    Then foo

Add stacklevel param to @given, @when, @then, @step

This will let users create functions that generate steps dynamically.

For example, if you use pytest-factoryboy, you can automatically generate steps for the model fixtures like this. This can be very useful when most of the model instance variables can be defined by similar looking "given" steps.

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

# contents of wallet.py

import dataclass

@dataclass
class Wallet:
    verified: bool

    amount_eur: int
    amount_usd: int
    amount_gbp: int
    amount_jpy: int

Pytest-factoryboy will automatically create a model fixture for this class:

# 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"):

# 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:

# 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:

# 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")

@codecov
Copy link

codecov bot commented Jul 29, 2022

Codecov Report

Merging #548 (9ecf4bf) into master (06499f7) will increase coverage by 0.07%.
The diff coverage is 96.22%.

@@            Coverage Diff             @@
##           master     #548      +/-   ##
==========================================
+ Coverage   95.17%   95.25%   +0.07%     
==========================================
  Files          48       48              
  Lines        1680     1708      +28     
  Branches      206      209       +3     
==========================================
+ Hits         1599     1627      +28     
  Misses         51       51              
  Partials       30       30              
Impacted Files Coverage Δ
pytest_bdd/steps.py 95.71% <87.50%> (ø)
pytest_bdd/__init__.py 100.00% <100.00%> (ø)
pytest_bdd/scenario.py 92.69% <100.00%> (-0.05%) ⬇️
pytest_bdd/utils.py 85.29% <100.00%> (ø)
tests/steps/test_common.py 100.00% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@youtux youtux marked this pull request as ready for review August 13, 2022 10:05
@youtux youtux added the feature label Aug 13, 2022
olegpidsadnyi
olegpidsadnyi previously approved these changes Aug 13, 2022
Copy link
Contributor

@olegpidsadnyi olegpidsadnyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@youtux youtux merged commit c5568af into master Aug 14, 2022
@youtux youtux deleted the step-advanced-usecases branch August 14, 2022 08:09
pbarnajc pushed a commit to pbarnajc/pytest-bdd that referenced this pull request Jan 31, 2023
pbarnajc pushed a commit to pbarnajc/pytest-bdd that referenced this pull request Feb 1, 2023
pbarnajc pushed a commit to pbarnajc/pytest-bdd that referenced this pull request Feb 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants