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

Allow parametrize to bubble up the fixture chain #4496

Closed
brunoais opened this issue Dec 2, 2018 · 7 comments
Closed

Allow parametrize to bubble up the fixture chain #4496

brunoais opened this issue Dec 2, 2018 · 7 comments

Comments

@brunoais
Copy link

brunoais commented Dec 2, 2018

Note: I don't know if this is a bug or a feature I'd like to have.

What I want:

When parametrizing a test (@pytest.mark.parametrize() with indirect=True, all fixtures in the "bubbling chain" that use request.param receive the parameters that were set by the test.

Example (which now fails):

@pytest.fixture(scope="function")
def higher_stage_test_fixture(request):
    return request.param * 2


@pytest.fixture(scope="function")
def test_fixture(request, higher_stage_test_fixture):
    return higher_stage_test_fixture * 4

@pytest.mark.parametrize(['test_fixture'], [
            pytest.param(1, id='1'),
            pytest.param(2, id='2'),
            pytest.param(3, id='3'),
    ], indirect=True)
def test_function(test_fixture):
    assert test_fixture % 4 == 0

The code fails at return request.param * 2.
AttributeError: 'SubRequest' object has no attribute 'param'

Tried workarounds:

  1. Changing indirect to be indirect=['test_fixture', 'higher_stage_test_fixture'], ofc, didn't work. The system requires that all values in indirect are specified as function parameters (first parameter in parametrize).
    1.1 Adding the parameter in the function didn't work (see 1.1.1)
    1.1.1. Adding the parameter in parametrize @pytest.mark.parametrize(['higher_stage_test_fixture', 'test_fixture'] ... as expected, was futile.
    Due to how parameterization is organized and prepared, changing that algorithm would be a huge loss in time.

Which lead to

Nasty-looking working workaround:

def higher_stage_test(request):
    return request.param * 2

higher_stage_test_fixture = pytest.fixture(scope="function")(higher_stage_test)

@pytest.fixture(scope="function")
def test_fixture(request):
    return higher_stage_test(request) * 4

@pytest.mark.parametrize(['test_fixture'], [
            pytest.param(1, id='1'),
            pytest.param(2, id='2'),
            pytest.param(3, id='3'),
    ], indirect=True)
def test_function(test_fixture):
    assert test_fixture % 4 == 0

There is code elsewhere calling higher_stage_test_fixture directly, for other tests. So it can't easily be changed now.
The workaround 2 does its job but it seems like going around a measure that was built not to happen (for good reason, the cannot call fixtures directly exception exists)

So, how can I "bubble up" the parametrization of test_function up to higher_stage_test_fixture without the nasty workaround?

Thanks in advance...

I thought this could be related to #460 but it seems to have some differences in the details of the call chain

@RonnyPfannschmidt
Copy link
Member

that kind of bubbling is absolutely unacceptable - it would make the fixture system more error prone to use and more error prone to implement

the correct way is to parametrize the fixture you actually want to hand a parameter to

@brunoais
Copy link
Author

brunoais commented Dec 2, 2018

You mean, to use:
@pytest.fixture(param=(...)))?

@RonnyPfannschmidt
Copy link
Member

@brunoais no - i mean using the name of the fixture that is supposed to get the parameters instead of the name of a fixture that uses it indirectly when using parameterize

@brunoais
Copy link
Author

brunoais commented Dec 2, 2018

That doesn't work either because (using the names in the example) test_fixture enhances higher_stage_test_fixture. test_fixture uses data from higher_stage_test_fixture, adds some extra transformation and returns.

Imagine as if it was this (incomplete-code)

@pytest.fixture(scope="function")
def generate_file(request):
    rules = generate_rules[request.param]()
	if rules.f:
		# ...
	return file

@pytest.fixture(scope="function")
def zip_compressed(request, generate_file):
    return zip_compress(generate_file)

@pytest.mark.parametrize(['zip_compressed'], [
            pytest.param(1, id='1'),
            pytest.param(2, id='2'),
            pytest.param(3, id='3'),
    ], indirect=True)
def test_zip_compressed_file(zip_compressed):
    zip_compressed_file(test_fixture)

@RonnyPfannschmidt
Copy link
Member

RonnyPfannschmidt commented Dec 2, 2018

@brunoais that example is fundamentally broken

a) if there was more than one fixture used we wouldn't know which one to bubble up
b) if the target had more than one source for bubbling we'd have a conflict
c) if the target was used in more than one place, it would create quite some surprise effects

also the example you showed would directly work right now by using generate_file as the indirect parameter target

@brunoais
Copy link
Author

brunoais commented Dec 2, 2018

also the example you showed would directly work right now by using generate_file as the indirect parameter target

You mean, making generate_file parametrized and then using two fixtures in the function call?

@pytest.mark.parametrize(['generate_file'], [
            pytest.param(1, id='1'),
            pytest.param(2, id='2'),
            pytest.param(3, id='3'),
    ], indirect=True)
def test_zip_compressed_file(generate_file, zip_compressed):
    zip_compressed_file(test_fixture)

?

@brunoais
Copy link
Author

brunoais commented Dec 2, 2018

In the end, I was just doing it wrong. I should have done like this in the first place:

@pytest.mark.parametrize(['generate_file'], [
            pytest.param(1, id='1'),
            pytest.param(2, id='2'),
            pytest.param(3, id='3'),
    ], indirect=True)
def test_zip_compressed_file(zip_compressed):
    zip_compressed_file(test_fixture)

->.parametrize(['generate_file'], <-
No need to specify it in the function signature.

I'll try finding the time to create an example for the documentation.... Such unfortunate that I didn't think about that possibility.

Oh well... My problem is then solved.

@brunoais brunoais closed this as completed Dec 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants