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

Fixture params not accessible inside the fixture, not getting called multiple times. #447

Closed
pytestbot opened this issue Feb 6, 2014 · 6 comments
Labels
type: bug problem that needs to be addressed

Comments

@pytestbot
Copy link
Contributor

Originally reported by: Paul Oswald (BitBucket: poswald, GitHub: poswald)


Parametrize decorator will not pass parameters into methods that are on subclasses of TestCase. There is a note at the bottom of http://pytest.org/latest/unittest.html#autouse-fixtures-and-accessing-other-fixtures explaining that this behaviour is intentional. I fully understand the logic behind that but I have quite a few tests that already are written in a way such that the methods accept (self, data) args by using a nose plugin. I need a function that works exactly like the parametrize decorator but will call TestCase methods.

In an attempt to work around this issue, I created the following proof of concept but it seems to have some issues:

#!python

from django.test import TestCase
import pytest

data_list = [
    {"1": 1},
    {"2": 2},
]

@pytest.fixture(scope='function', params=data_list)
def my_data(request):
    print request.param

@pytest.mark.usefixtures("my_data")
class TestParameterizedTest(TestCase):

    def test_passing(self):
        assert 1 == 1

    def test_multiple(self):
        print self.my_data
        assert 1 == 2

When this runs request.param is not defined. Also, request.param is mentioned in the docs but not really listed in the request api.

request = <SubRequest 'my_data' for <TestCaseFunction 'test_passing'>>

    @pytest.fixture(scope='function', params=data_list)
    def my_data(request):
>       print request.param
E       AttributeError: SubRequest instance has no attribute 'param'

Also, even if you don't try to access request.param, I expected this to parametrize into 2 tests and it doesn't seem to recognise it as being parameterized.


@pytestbot
Copy link
Contributor Author

Original comment by Ronny Pfannschmidt (BitBucket: RonnyPfannschmidt, GitHub: RonnyPfannschmidt):


not supported on unittest subclasses

try a autouse fixture or go for non-unittest

@pytestbot
Copy link
Contributor Author

Original comment by Paul Oswald (BitBucket: poswald, GitHub: poswald):


I can't get autouse to work either. Of course, I could go for non-unittest for new code but I already have hundreds of tests that use nose_parameterized that I would like to convert. Because py.test refuses to break the unittest convention and the nose parameterize plugin did, there's no clean way to port from these tests from nose to py.test.

Can you think of a way to allow unittest subclass methods to still be parameterized by py.test? a custom plugin?

@pytestbot
Copy link
Contributor Author

Original comment by Jurko Gospodnetić (BitBucket: jurko, GitHub: jurko):


Just a thought... how about working around it with something like this:

#!python
import pytest

data_list = [
    {"1": 1},
    {"2": 2}]

@pytest.fixture(scope='function', params=data_list)
def my_data(request):
    if request.cls:
        request.cls.my_data = "%s-%s" % (request.fixturename, request.param)
    print(request.param)

@pytest.mark.usefixtures("my_data")
class TestParameterizedTest:

    def test_passing(self):
        assert 1 == 1

    def test_multiple(self):
        print(self.my_data)
        assert 1 == 2

You can use autouse instead of @pytest.mark.usefixtures as well, and it should work the same.

You just replace "%s-%s" % (request.fixturename, request.param) used in that example with whatever else you actually want to pass as data to your test functions.

Also, I do not think this behaviour is anything unittest specific. I tested it using a regular pytest test class (just an ordinary class whose name starts with Test) and that seems to have the same behaviour.

I think the main problem you are running into here is that with pytest, a test class method can only access its fixture if a reference to it is passed as an explicitly specified argument. pytest does not attempt to store a reference to the fixture in a test object's attribute.

Hope this helps.

Best regards,
Jurko Gospodnetić

@pytestbot
Copy link
Contributor Author

Original comment by Jurko Gospodnetić (BitBucket: jurko, GitHub: jurko):


I extracted the part about inconsistent docs into a separate issue #487.

@pytestbot
Copy link
Contributor Author

Original comment by Jurko Gospodnetić (BitBucket: jurko, GitHub: jurko):


@poswald - I do not really get what you meant to say by:

#!text
Also, even if you don't try to access request.param, I expected this to
parametrize into 2 tests and it doesn't seem to recognise it as being
parameterized.

I get the following tests collected in my example whether or not I access request.param from the fixture function code:

#!python
D:\Workspace>py3 -m pytest aaa.py --collect-only
============================= test session starts =============================
platform win32 -- Python 3.4.0 -- py-1.4.20 -- pytest-2.5.2
collected 4 items
<Module 'aaa.py'>
  <Class 'TestParameterizedTest'>
    <Instance '()'>
      <Function 'test_passing[my_data0]'>
      <Function 'test_passing[my_data1]'>
      <Function 'test_multiple[my_data0]'>
      <Function 'test_multiple[my_data1]'>

==============================  in 0.03 seconds ===============================

so both test methods get collected twice - once for each fixture parameter. Exactly as I would expect them to.

Best regards,
Jurko Gospodnetić

@pytestbot
Copy link
Contributor Author

Original comment by Paul Oswald (BitBucket: poswald, GitHub: poswald):


Thanks for the suggestion and opening that issue for the docs. Sorry I'm not being clear enough. The issue with that is that it no longer works if you inherit from TestCase.

#!$ py.test core/tests/test_params.py --collectonly
============================================================================== test session starts ===============================================================================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2
plugins: django, xdist
collected 2 items
<Module 'core/tests/test_params.py'>
  <UnitTestCase 'TestParameterizedTest'>
    <TestCaseFunction 'test_multiple'>
    <TestCaseFunction 'test_passing'>

As Ronny said above, this is mostly by design. (as explained in the note at the bottom of the docs. I respect that design decision although I don't agree with it. (I think that if a developer explicitly marks a method with a decorator and that breaks the unittest API contract, then it's the developer's fault. I would rather the py.test let me take responsibility for that.)

My problem is that I have a ton of classes (TestCase, pythonpaste WebTest, etc..) that already break the unittest style by being parametrized via a nose plugin and I'm trying to find a way to disable or work around this py.test safety mechanism.

Mostly, I do think this issue is resolved because I understand how it works now and it's basically 'as designed'. It just doesn't solve my problem. Right now I'm investigating if I can write a new unittest plugin to replace the built in one that works exactly like it but doesn't have this feature..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug problem that needs to be addressed
Projects
None yet
Development

No branches or pull requests

1 participant