pytest-dev / pytest-bdd

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

give all the steps with same name #142

Closed learner010 closed 9 years ago

learner010 commented 9 years ago

How can I use same name for all the steps? I feel the function names are meaningless once they are decorated by @given, @then, @when. When I name all the steps to '', I get `Fixture already in useexception. How can I instructpytest-bdd` not to consider steps as fixture?

spinus commented 9 years ago

@learner010 'given' (I'm not sure only those) steps are also pytest fixtures by default so they are registered with their name so then you can use them as argument to any pytest test function (look at pytest-bdd examples). This makes impossible to use the same name twice (at least for given steps).

olegpidsadnyi commented 9 years ago

That's right. And I wonder what is the use-case of making all given steps having the same name. If they are applying the side-effect only? I believe there is a case when some other when/then step would need the value returned by a given step. This is the exchange of the information via the pytest request as a context.

learner010 commented 9 years ago

I personally feel it very confusing. It might be useful in some context. At present, I create a scope fixture and attach every new values to it and pass it around. I am new to pytest and pytest-bdd. I would love to learn about the best practice for fixture.

Below is how I am handling the scope:

scenarios('../features/prepare_request.feature')

@pytest.fixture(scope='function')
def scope():
    class Scope(object):
        pass
    return Scope()

@given('a url')
def set_url(scope):
    scope.url = 'http://awesome.com'

@given('method is GET')
def set_method(scope):
    scope.method = 'get'

@when('the function is called with the url and method')
def make_the_request(scope):
    scope.request = make_request(scope.url, scope.method)

@then('the function should return the tuple with an instance of requests.Request')
def check(scope):
    assert type(scope.request) == requests.Request
olegpidsadnyi commented 9 years ago

PyTest request is the scope. Fixtures are variables of this context. It seems you are using BDD with very deep detailization as a script. But still it is possible to give some fixtures values with the step. What's your full .feature file? I wonder how you are using it

olegpidsadnyi commented 9 years ago

For example if you are implementing primitives such as method fixture:


Given the method is "GET"

@given(parsers.parse('the method is "{method}"'))
def set_method(method):
    return method

You see - the step argument is also a fixture. So you will be able to use it in the further steps:


@when('the function is called with the url and method')
def make_the_request(url, method):
    scope.request = make_request(url, method)

@given(parsers.parse('the method is "{method}"'))
def set_method(method):
    """Will also work without returning any value for the set_method fixture. Method fixture is enough."""

P.S. Gherkin is for the just-enough specificaiton. It seems you are specifying what the request will be. In fact the only input which is needed is the URL and the method. Request is just an intermediate step to get the result. So I'd advice you to write when step where you rather fetch the result. Otherwise it all seems programming python in text files instead specifying the behavior, which is a lot higher level.

learner010 commented 9 years ago
Feature: Request a URL

Scenario: Makes GET request to a URL
    Given a url
    And method is GET
    When the function is called with the url and method
    Then the function should return the tuple with an instance of requests.Request

The feature is something like this. I dont think we can extract object instance from scenario as u did with {method} above. I am still learning BDD, might be my approach is too verbose or too immature. Any suggestion is appreciated. :)

olegpidsadnyi commented 9 years ago

I believe you should specify the behavior of your entities. It looks you are testing some API. This API perhaps implements some resources, some entities. It could be CRUD or REST whatever, but the bit or URL probably identifies the resource.

Lets imagine your resource is a Book (in the Library API).


Feature: Library API Book CRUD
Scenario: Get a book
    Given there's a book  # In fact can apply some side effect and insert the book in the database
    And the book title is "BLA"
    When I get the book  # This performs a GET call with the url that includes the resource id mentioned in given
    Then the response book titile should be "BLA"

I know ppl used rest navigator instance as a fixture and the result of the request was cached there so you can always access it. You can still store your result in the dict fixture like you did with the context. The problem is that pytest request is the context and should be rather computed and returned than mutated and passed along.

Gherkin should specify your system. Not be a script language. You should mention concrete resources and the actions on them. Gherkin should reflect the semantics of your system, describe it, but not to script it as get/post with the parameters.

spinus commented 9 years ago

Anyway, I think this issue can be closed becuase this probably wont be implemented anyway, right? @learner010 I think if you want this feature, simplest thing is to write simple DSL (like python konira project did).

learner010 commented 9 years ago

ya thanx :)