-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Fixture "..." called directly #3950
Comments
the best way would be fixing your code - so you no longer call an actual fixture directly, however you also found us a bug, - the error should point to the call location, not to fixtures.py |
Sometimes I want to reuse the code of the fixture and/or parameterize it, e.g.:
Above, I used it as class-wide fixture (
Furthermore, I could also pass additional parameters in case of test-method usage (e.g. With this new release, you force me to:
If there are no maintainable alternatives to my requirements, then I propose |
the correct and dry way is to extract the fixture insides aka something like the untested : @pytest.fixture
def ma_fixture():
return make_ma()
def make_ma(*k, **kw)
rd = numpy.random.random(LEN)
ma = stats.MovingAverage(rd, *k, **kw)
return rd, ma
@pytest.mark.parametrize('execution_number', range(EXECUTION_NUMBER))
class TestMovingAverage(object):
@pytest.fixture(autouse=True, scope='class')
def fixture(self, request):
request.cls.rd, request.cls.ma = make_ma()
def test_bool(self, execution_number):
assert self.ma
def test_len(self, execution_number):
assert len(self.ma) == len(self.rd)
def test_iadd(self, ma_fixture, execution_number):
rd, ma = ma_fixture
mean = ma.mean
e = rd[0]
ma += e
assert len(ma) == len(rd) + 1
assert ma.count == len(rd) + 1
assert ma.mean == mean + (e - mean) / len(ma) |
That's what I mentioned in the first item. I can of course redo everything this way but it's an overkill since |
@Alexander-Shukaev the main problem is that fixture declaration is a massive point of unintended mess ups - there is simply so much going wrong with leaving it be a normal function that we decided to deprecate having the fixture definition be a function - people just use it wrong again and again and again |
I see, then my last question would be: does it make sense to turn this warning into an error with the next major bump? |
it was introduced to turn it into an error at the next major bump |
If we would allow for |
@blueyed im strictly opposed - thats as complex as literally having just the function extracted completely, just extract all if the function, there should be a helper to create a new fixture definition from a function perhaps - but there shouldn't be a |
aka this is also to prevent the "i know what I'm doing" people from handing a loaded safety off foot-gun to junior members of a dev team (i observed on multiple occasions that this kind of mistake even slip good python developers in review - since its just a function, so the code they see isn't "wrong" yet it is - a good api makes really bad behavior impossible - and as such fixture definitions should never be normal function - as people will use it as such, and then face the "surprise" of unexpectedly fragile tests as the aftermath |
Yes, good points. Let's not add more complexity / hold on to the plan to remove it. |
What about the case of inheritance?
Will this pattern be explicitly not allowed? |
@tim-schilling it will be disallowed, because while for really simple things it does work, for more complex fixtures it is generally structurally broken |
I was also greatly surprised that direct fixture calling is deprecated. To me, that is one of power features! Please consider how useful and readable it is: @pytest.fixture
def client(request):
server = request.config.getoption('--server')
if server == 'app':
yield from app_client()
elif server == 'local':
yield from local_client()
elif server == 'docker':
yield from docker_client(request)
else:
yield from running_client(request) where It is disturbing that useful feature is deprecated just because some hmmm... under-qualified engineers use it incorrectly. |
@aparamon just a fyi - your code is full on broken and the moment someone uses 2 of those fixtures at the same time they are at best lucky if nothing blows up the correct api to select different named fixtures is just let it sink in for a while that while you complain about "under-qualified" people using it incorrect, you did as well the problem here is that well-qualified people use it incorrectly since the underlying behavior contracts are unexpectedly complex - its basically backstabbing you as is |
also note, if you make the server option a choice between the valid client names and set the default to your fixture will turn into @pytest.fixture
def client(request):
server = request.config.getoption('--server')
return request.getfixturevalue(server + '_client') |
@RonnyPfannschmidt Thank you for your comments and feedback! I cannot imagine a use-case for both Is that correct that my code can be rewritten as @pytest.fixture
def client(request):
server = request.config.getoption('--server')
if server == 'app':
return request.getfixturevalue('app_client')
elif server == 'local':
return request.getfixturevalue('local_client')
elif server == 'docker':
return request.getfixturevalue('docker_client')
else:
return request.getfixturevalue('running_client') to avoid any subtle possibility of unexpected behavior, and thus warnings? It becomes less straight, but if it's fine I believe I can live with it. Shouldn't deprecation warning refer to |
@aparamon, your new version is the recommended one IMO. 👍
It really depends on how the fixture is being used, always mentioning @pytest.fixture
def cell():
return ...
@pytest.fixture
def full_cell():
cell = cell()
cell.make_full()
return cell |
@nicoddemus Thanks for the pointer! |
@Alexander-Shukaev good point, addressing it there could be a great help for others 👍 |
Good idea, would you like to submit a PR? |
@nicoddemus Not immediately, so if someone wishes to do it please go ahead! |
I'm trying to contribute to one pytest plugin, but I see that its own tests are calling the fixtures directly and raising the warning. Should |
@butla It is a matter of preference, both will work. Another alternative is to create an indirection: def fix_value():
return FixtureValue()
@pytest.fixture
def fix():
return fix_value()
def test_foo():
value = fix_value() |
With pytest v4, we would need to rewrite our entire test suite, as fixtures can’t be function anymore. See also e.g. pytest-dev/pytest#3950 For now therefore limiting pytest versions to <4.0
FWIW, I find the original (I guess incorrect) usage in the example above more readable. No hard coded string and you can click through to the function in your IDE.
This change broke hundreds of tests when a build pulled in a more recent version of pytest. And the author is gone, so now I get to go through all this to try and "fix" it. I guess I understand the desire to encourage a better usage, but breaking changes really suck. |
We always introduce warnings before turning things into actual errors to minimize this pain. But we feel you, it does really sucks when things that are working break, even if we intend to improve the overall experience. |
I have now looked into https://github.com/pytest-dev/pytest/blob/master/src/_pytest/fixtures.py, and the warning is produced in But what is the actual reason not to call the implied It might be surprising that function call re-uses cached return-value, but that's not different from e.g. functools.lru_cache: you do expect some "black magic" from certain decorators. |
@Alexander-Shukaev |
* fix naxis access * removed _naxis and fixed np.where with units problem * small approx in assert unit test * shouldn't call fixtures e.g. pytest-dev/pytest#3950 (comment) * maybe fix to array_sequence_equal * fix fixture names test_psf * removed duplicated test functions * use more recent packages travis * Revert "use more recent packages travis" This reverts commit 6c55f18.
Not dry at all |
You also dropped possibility of using it from lambdas with this change. |
Best solution I came up with is, if you want some fixture to be called both as fixture and directly, you remove def fixture():
...
def use(fixture=fixture()):
... or (if you still want to use fixture.__wrapped__() |
After upgrading to
3.8.0
, I keep getting thewarning message. Is there a way to suppress it?
The text was updated successfully, but these errors were encountered: