pytest-bdd: pytest_bdd.exceptions.GivenAlreadyUsed with step arguments
From the readme:
Often it’s possible to reuse steps giving them a parameter(s). This allows to have single implementation and multiple use, so less code. Also opens the possibility to use same step twice in single scenario and with different arguments!
But this doesn’t seem to be the case for @given
. My usecase is this feature file:
Feature: Going back and forward.
Testing the :back/:forward commands.
Scenario: Going back
Given I open backforward/1.html
And I open backforward/2.html
When I run :back
Then backforward/1.html should be loaded
and this python file:
import pytest_bdd as bdd
bdd.scenarios('.')
@bdd.given(bdd.parsers.parse("I set {sect} -> {opt} to {value}"))
def set_setting(quteproc, sect, opt, value):
quteproc.set_setting(sect, opt, value)
@bdd.given(bdd.parsers.parse("I open {path}"))
def open_path(quteproc, path):
quteproc.open_path(path)
@bdd.when(bdd.parsers.parse("I run {command}"))
def run_command(quteproc, command):
quteproc.send_cmd(command)
@bdd.then(bdd.parsers.parse("{} should be loaded"))
def url_should_be_loaded(httpbin, path):
requests = httpbin.get_requests()
assert requests[-1] == [('GET', url)]
So opening those two pages is clearly “setup”, i.e. I think they both belong to Given ...
.
But I get:
pytest_bdd.exceptions.GivenAlreadyUsed: Fixture "open_path" that implements this "I open backforward/2.html" given step has been already used.
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Comments: 48 (22 by maintainers)
Reviving this issue, I’m bumping into the same problem, testing a tree-like data structure:
Given there is a root with value X And there is a node with value Y And there is a node with value Z …
The second “And” step triggers that error.
Another case: Given I have a garage named “Total Express” And there is a car with license plate “123-678” and the make is Toyota And its fuel tank has 32 gallons And there is another car with license plate “456-333” and the make is Ford And its fuel tank has 12 gallons …
The “another” is superfluous, and I need to implement it separately, causing duplicate code. The “its fuel tank has … gallons” throws an error. Yes, I could merge the “fuel tank” into the “there is a car” rule, but I prefer to keep those separate so I can reuse the “there is a car” clause in other scenarios where the fuel tank does not matter.
So bottom line, this limitation is annoying. At the very least add a configuration to relax it (perhaps even as part of the “strict gherkin” relaxing". I don’t mind fixing this… would it be okay?
I encountered this problem as well.
I see there are various opinions on the topic, and I agree with @olegpidsadnyi that the
given
should be data.I see it as:
given
: the precondition (there is no action to do, this describes the state of the system)when
: action under testthen
: expected post condition of the actiongiven
being data is perfectly reasonable, andpytest
gives a lot of flexibility on how to create that data.The current behavior though makes it hard to describe a system which has a variable amount of data to create.
This is such a common need that
pytest
explicitly mentions it in it’s own documentation: https://docs.pytest.org/en/latest/fixture.html#factories-as-fixturesIn this case a fixture is used as producer of objects, because tests might require a variable number of objects, or objects created with different parameters.
This is a very simple example
While trying to address the problem, I modified
pytest-bdd
to allowgiven
to be used as a factory. We can still access the data created bygiven
with the fixture name inthen
andwhen
, but we are getting a list which contains all the data created by the given, so that it can be referenced by the steps.If you think this is a solution that fits the spirit of the project, I can look into cleaning up the PR and submit it. At the moment it’s a fairly small code change, and it’s backward compatible with the current behavior.
I think this is a very powerful feature, which brings
pytest-bdd
closer to feature parity withbehave
, plus all the power ofpytest
.@olegpidsadnyi I’ve given this some more thought and discussed with colleagues, and I understand the problem a bit better now, and the solution to it. Making Given steps always fixtures does indeed cause problems, and although it’s going to be additional work for me to fix up my tests, it is a “step” (pun intended) in the right direction, so I withdraw my request to roll back the changes. Thank you for your time and explanations.
Agreed, this is pretty annoying.
This is supported in
cucumber-js
, and I can attest to it making testing a lot easier and more fun.Hello,
Could you consider to relax this constraint by using a parameter (like
strict_gherkin=False
)?This is restriction is really painful. For instance: this step is to inject value in an automotive bus, different named signals have to be sen in the same scenario:
The BDD scenario is:
For me, the steps
Given BUS signal [NHVACACLampReq] is equal to [1]
andGiven BUS signal [NHVACACPowerReq] is equal to [2.6]
are strictly different and should be authorized.I’ve also encountered it right now where the most intuitive option was to reuse the given statement but I got this error and I need to make a hack to make it work. It could be nice if this will be supported in the near future 😃
I also came here and after reading the whole thread, the solution is not obvious. But I finally get how to make it work by respecting the fact that in pytest-bdd, given is data and will become a pytest fixture.
Maybe a new section could be added to the documentation with the following example (or your own).
Let’s say I want to test a cli application, I could want to write the following scenario
With the code:
This will fail in pytest-bdd as the given method would be executed twice and is not allowed.
I can think of 2 ways to solve that.
First possibility, combine the 2 given statements together
with the code
Second possibility, convert the given statements into when statements. Once I got past the feeling it is wrong to have multiple when statements (which purists will probably say), it made my tests much easier to write
I hope this can help other people getting stuck with this issue.
I bumped in the same conceptual problem.
This is my conclusion (for the records so who’s going to bump into it again might see it from another angle).
Pytest use fixtures, which are ‘data’. The various verbs in the BDD feature file are mapped into fixutures when using pytest-bdd.
the expected result is:
and can be mapped into
and this works.
But, since there is the possibility of using parsers, there is the strong temptation of parametrising things and create
This turns the ‘given’ verb into a data-factory, not data. Which other BDD products manage (maybe?) this way. pytest-bdd cannot distinguish if we are talking about a data-factory fixture which returns data, or a data fixture itself.
One approach is to just consider fixtures as data. The other approach is to allow parametrization and high flexibility creating those data-factories. This is a matter of product’s pholosophy and maybe BDD ‘purity’.
The former avoids the misuse of the data associated to the verbs, since (AFAIK) the BDD feature syntax is a Product Owner (or whateve is called in the method you use) tool, not a generic testing tool.
The latter approach doesn’t associate necessarily a BDD verb to a data, but the BDD verb creates the data nevertheless: the data is stored for later retrieval somehow. There is association, but not strong coupling one-verb=one-data. This can lead to a diiferent use of BDD given-when-then verbs, into really program-like steps which could be seen as a distorsion of the BDD ideas.
Also, from the technical point of view, the latter is incompatible with the concept of fixtures in pytest (at least as they are used within pytest-bdd) and the fact that pytest-bdd strongly couples fixtures with verb’s data makes it not possible to have parametrised flexibility in it (aka generic data generation from rules, instead of fixed data points on which to work)
anyway, all this to say: don’t confuse data and data-factories. Both are cool, as long as we are aware those are different concepts. pytest-bdd doesn’t cope well with factories, and that’s probably good depending on how you use BDD
my 2p.
It is pytest-bdd, so the setup is done via fixtures
Maybe we can add to given() function a flag (for example, reusable=True/False or smth like this) and add to documentation a big warning that in this case given() function ceases to be a fixture and become as a plain function?
@smoustaj I don’t really understand your problem, but it seems you are using given section to take exercise. Given is 1 fact. You can’t repeat it, it is given. For example “Given my article has 5 comments”. If you repeat it 5 times do you expect 25 comments? Exercise must be taken in the When section.
There just was someone in the
#pylib
IRC channel who wanted to do the same (usingGiven
for arbitrary setup steps, even if they are based on side-effects). They used freshen for nose before, and there that apparently worked without issues.To be honest, I’d still also like to do the same - using Given/When/Then for setup/testing/teardown.
@olegpidsadnyi - would you be okay with a patch to turn that check off? I’m reopening this for now 😄