pytest-dev / pytest-bdd

BDD library for the pytest runner
https://pytest-bdd.readthedocs.io/en/latest/
MIT License
1.31k stars 221 forks source link

parsers.parse with scenario outline - pytest_bdd.exceptions.StepDefinitionNotFoundError #293

Open Vic152 opened 5 years ago

Vic152 commented 5 years ago

Hey Guys,

I have a step in scenario outline:

Given something
When thing <thing> is moved to position <position>
Then something else
Examples:
|thing|position|
|1    |1       |

In my glue code I write @when(parsers.parse('thing {thing:d} is moved to position {position:d}'))

I when an error: pytest_bdd.exceptions.StepDefinitionNotFoundError: Step definition is not found: When

I tried When thing {thing} is moved to position {position}

When I use is passes it as a string. I though if I use parse I could make it an int. Is there some special way feature file has to be written?

sliwinski-milosz commented 5 years ago

Hi,

You should define scenario outlines in following way:

Scenario Outline: Some outline
    Given something
    When <thing> is moved to <position>
    Then something else

    Examples:
    | thing | position |
    |   1   |    1     |

As you can see thing and position are in between <>

I don't know if you can use scenario outlines together with parsers. Example in documentation shows another way of specifying converters. In your case:

@scenario(
    'yours.feature',
    'Some outline',
    example_converters=dict(thing=int, position=int)
)
def test_outlined():
    pass

...
@when('<thing> is moved to <position>')
def thing_is_moved_to_position(thing, position):
    assert isinstance(thing, int)
    assert isinstance(position, int)
Vic152 commented 5 years ago

Good to know there is a way to do this like below:

@scenario( 'yours.feature', 'Some outline', example_converters=dict(thing=int, position=int) )

I may cast them too... I just thought you could use parser to post-process them. I think there is a value to what you proposed if u use <thing> in several steps. Thanks a lot!

As you can see thing and position are in between <>

Sorry formatting in GitHub

3rd edit: Also, it would be nice if example_converters were mentioned in parser section of the documentation :)

Evolter commented 5 years ago

Hi, I did get the same error when I was trying to combine "step arguments" with "scenario outlines". I'm not 100% sure if this is a correct way of doing it, but with pytest-bdd==3.1.0 I manage to make this working like so:

  Scenario Outline: Basic DuckDuckGo API Query
    Given the DuckDuckGo API is queried with "<phrase>" using "json" format
    Then the response status code is "200"
    And the response contains results for "<phrase>"

    Examples: Animals
    | phrase   |
    | panda    |
    | python   |
    | platypus |

    Examples: Fruits
    | phrase     |
    | peach      |
    | pineapple  |
    | papaya     |
scenarios('../features/service.feature', example_converters=dict(phrase=str))

@given(parsers.parse('the DuckDuckGo API is queried with "<phrase>" using "{fmt}" format'))
def ddg_response(phrase, fmt):
    params = {'q': phrase, 'format': fmt}
    response = requests.get(DUCKDUCKGO_API, params=params)
    return response

@then('the response contains results for "<phrase>"')
def ddg_response_contents(ddg_response, phrase):
    assert phrase.lower() == ddg_response.json()['Heading'].lower()

@then(parsers.parse('the response status code is "{code:d}"'))
def ddg_response_code(ddg_response, code):
    assert ddg_response.status_code == code

Can anyone confirm if this is the way to do this or am I just lucky that it works as I want it? :)

david-house-harvard commented 5 years ago

@Evolter I can confirm that parse is compatible with scenario outlines. For me, the problem arose trying to migrate steps from behave to pytest-bdd.

Take this example:

    Scenario Outline: Animals are cute

        Given a <animal>
        When the <animal> is a baby
        Then the <animal> is cute

        Examples:
        | animal |
        | dog    |
        | cat    |

    Scenario: Some animals are cuter

        Given a puppy
        When the puppy is a baby
        Then the puppy is cuter than cats

In behave, you could represent both of these features with just a few steps:

from behave import given, when, then

@given('a {}')
def step_impl(context, animal):
    pass

@when('the {} is a baby')
def step_impl(context, animal):
    pass

@then('the {} is cute')
def step_impl(context, animal):
    pass

@then('the {} is cuter than {}')
def step_impl(context, animal, other_animal):
    pass

However, in pytest-bdd, the order of operations for translating .features to steps is different. You need to use step aliases to achieve the same result:

from pytest_bdd import given, when, scenarios, then
from pytest_bdd.parsers import parse

scenarios('animals.feature')

@given(parse('a {animal}'))
@given('a <animal>')
def animal(animal):
    pass

@when(parse('the {animal} is a baby'))
@when('the <animal> is a baby')
def animal_is_baby(animal):
    pass

@then(parse('the {animal} is cute'))
@then('the <animal> is cute')
def animal_is_cute(animal):
    pass

@then(parse('the {animal} is cuter than {other_animal}'))
def animal_is_cuter(animal, other_animal):
    pass

Personally I prefer the capabilities behave in this case, but it's not a big deal to have to add a bit of aliasing. It just makes the step code a little less DRY.

nicois commented 5 years ago

I have also found the above, which feels really silly to me.

In addition to the syntactic ugliness of having to decorate evey step function twice, if you want it to work with a tabulated 'scenario outline' syntax in addition to the standlone 'scenario' syntax, it also doesn't properly respect parameter names. By this I mean you are forced to use the identical parameter names in your python step functions as is used in the outline. In my mind, this is unnecessary friction.

For example, if a tester writes

 Scenario: SingleInput Validation
        Given I am on the patient registration page
        When I enter 12345 into mobilePhone
        And I lose focus
        Then I should see an error state on the mobilePhone field

and

   Scenario Outline: Input Validation
        Given I am on the patient registration page
        When I enter <foo> into <field_name>
        And I lose focus
        Then I should see an error state on the <field_name> field

        Examples:
            | field_name  | foo  |
            | mobilePhone | 01234  |
            | homePhone   | 12391  |
            | email       | rjiojr |

and the step is defined as

@when(parse("I enter {value} into {field_name}"))
@when("I enter <value> into <field_name>")
def enter_form_value(page_model: BasePage, value: str, field_name: str) -> None:
    page_model.modify_form_field(field_name=field_name, value=value)

.. then the second situation fails, as 'foo' is required in the signature of enter_form_value.

In my mind, the solution is for the scenario outline to "preprocess" the feature file before the individual lines are parsed. This would solve both problems: the proper parser decorator would work for everything, and the gherkin administrators can use any placeholder text for the dynamic aspects of the BDD. Even better, the aspects of the BDD step which are parameterised would not even need to correspond to single step functions! It may be that there are a collection of different step functions with similar but not identical BDD step text; this would allow the testers to refer to different step functions from the same outline!

nicois commented 5 years ago

I will attempt to resolve this and make a PR.

nicois commented 5 years ago

https://github.com/pytest-dev/pytest-bdd/pull/302

The tests demonstrate what I've done. Here you can see the given/when/then steps defined using the normal parse decorator. Note the parameter names: https://github.com/nicois/pytest-bdd/blob/master/tests/feature/test_outline.py#L164

Now compare that to the syntax in the scenario outline. There is no longer a need for the names used in the outlines to be the same as the step functions' argument names. You are even free to define your steps completely separately, as long as they have a common syntax which matches! https://github.com/nicois/pytest-bdd/blob/master/tests/feature/outline_modern.feature

nicois commented 5 years ago

Spending some more time with this, I'm having second thoughts about whether the above PR is an overall step forward (pun intended).

The issue is that if we implement the above, you then need to take a lot more care with the syntax of scenario outlines. For example. the following syntax will not work as cleanly, particularly if some of the parameters can be empty strings:

given a <foo> <bar> <baz>

you would need to add delimiters/quotes or similar to the BDD syntax to support this:

given a '<foo>' '<bar>' '<baz>'

... which won't play nicely with quotes in the actual argument values.

In short, the way I'm seeing it now, the price we would pay with my PR is probably too high to warrant it. I really don't like the dual wrapping of step functions referred to both from scenarios and scenario outlines, but I think we're stuck with it.

nicois commented 5 years ago

Partly in response to this problem, I have started to make an alternative pytest plugin to support gherkin files. It attempts to address a few shortcomings of pytest-bdd, such as the problem of mixing steps in outlines and non-outlines. If you're interested in seeing the direction I am headed, take a look here: https://github.com/nicois/pytest-gherkin/tree/develop/tests

bigbirdcode commented 4 years ago

Dear pytest-bdd team! I'm about to start a larger test project with bdd style testing. In pytest-bdd, the scenario outline handling was a show stopper for me. I was thinking to re-write it, but then I found the solution of @nicois . I tested it with my test code base and it worked perfectly for me. Regarding the last doubt of @nicois (given a ) is not an issue, it is a generic limitation of Gherkin anyway. I also reviewed the open issues, and found that many issues are about scenario outline handling and reporting ( #177 , #199 , #235 , #247 , #326 ). I think this solution would solve many of them. To conclude I would really like to ask to solve this issue and merge this PR into the main branch. Thanks in advance.

elchupanebrej commented 3 years ago

404

Jarikf commented 3 years ago

This problem also affects our team. Migrating from squish (which is behave-like considering the BDD logic) We'd very much like to use the same step definition for both following cases.

Then picture on scene is '<file_name>'
Then picture on scene is 'test20878/result1.png'
sokol11 commented 2 years ago

Hi. New user here. I think I may be having a related issue.

I have the following feature definition:

Feature: Data
    A program that handles various data.

    Background:
        Given a Data subclass

    Scenario Outline: Instantiating a data subclass
        When a <method> method is undefined
        Then raise a TypeError on instantiation

    Examples:
    | method    |
    | download  |

When I run:

@when('a <method> method is undefined')
def method_is_undefined(data_subclass, method):
    assert method in data_subclass.__dict__['__abstractmethods__']

I get the aforementioned pytest_bdd.exceptions.StepDefinitionNotFoundError error.

How can I fix this?

Thank you.

Vic152 commented 2 years ago

I think you are missing Given in your Scenario Outline. Try to put Given in the scenario outline and let us know if it worked. I would probably not use Background for scenario that you showed us.