Skip to content

Class scope in parametrized fixtures does not work if first class to be collected has only one method using that fixture #396

Closed
@pytestbot

Description

@pytestbot

Originally reported by: BitBucket: rajaram_s, GitHub: rajaram_s


When using class scope with parametrized fixtures, if the first class to be collected has only one method that uses that fixture, then the scope value is not honored. Instead objects are created and cleaned up for every test method execution as if the scope value was set to 'function'

#!python

# parametrized_fixtures.py

import pytest


class Person:
    population = 0

    def __init__(self, name):
        self.name = name
        print("\nNew Object for %s : %s" % (name, self))
        Person.population += 1

    def __del__(self):
        print 'Object for %s is deleted.' % self.name
        Person.population -= 1

    def getName(self):
        return self.name

    def sayHello(self):
        return 'Hello! my name is %s.' % self.name

    def count(self):
        return Person.population


@pytest.fixture(params=["John", "Doe"], scope="class")
def human(request):
    name = request.param
    human = Person(name)

    def finalizer():
        print("\nFinalizer called for %s" % human)
    request.addfinalizer(finalizer)
    return human


class TestGreetings:
    def test_hello(self, human):
        assert human.sayHello()

    #def test_bye(self, human):
    #    pass


class TestMetrics:
    def test_name_length(self, human):
        assert len(human.getName()) > 0

    def test_population_count(self, human):
        assert human.count() >= 0

When running the above code with the command py.test -s -v parametrized_fixtures.py the output is

#!python
=
platform linux2 -- Python 2.7.3 -- pytest-2.4.2 -- /usr/bin/python
plugins: xdist
collected 6 items

parametrized_fixtures.py:44: TestGreetings.test_hello[Doe]
New Object for Doe : <parametrized_fixtures.Person instance at 0x2492dd0>
PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2492dd0>

parametrized_fixtures.py:52: TestMetrics.test_name_length[John]
New Object for John : <parametrized_fixtures.Person instance at 0x24994d0>
PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x24994d0>

parametrized_fixtures.py:52: TestMetrics.test_name_length[Doe]
New Object for Doe : <parametrized_fixtures.Person instance at 0x2499908>
PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2499908>

parametrized_fixtures.py:55: TestMetrics.test_population_count[John]
New Object for John : <parametrized_fixtures.Person instance at 0x24998c0>
PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x24998c0>

parametrized_fixtures.py:55: TestMetrics.test_population_count[Doe]
New Object for Doe : <parametrized_fixtures.Person instance at 0x2499fc8>
PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2499fc8>

parametrized_fixtures.py:44: TestGreetings.test_hello[John]
New Object for John : <parametrized_fixtures.Person instance at 0x2499950>
PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2499950>


=======================6 passed in 0.02 seconds ======================
Object for John is deleted.
Object for Doe is deleted.
Object for John is deleted.
Object for Doe is deleted.
Object for John is deleted.
Object for Doe is deleted.

Instead** if the class 'TestGreetings' has 2 methods** that take the fixture 'human' as input argument,** then things work fine as expected**. For each object returned by the fixture function, all the test methods in a class get executed before repeating the same for the next object.

#!python
=
platform linux2 -- Python 2.7.3 -- pytest-2.4.2 -- /usr/bin/python
plugins: xdist
collected 8 items

parametrized_fixtures.py:44: TestGreetings.test_hello[John]
New Object for John : <parametrized_fixtures.Person instance at 0x2672ea8>
PASSED
parametrized_fixtures.py:47: TestGreetings.test_bye[John] PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2672ea8>

parametrized_fixtures.py:44: TestGreetings.test_hello[Doe]
New Object for Doe : <parametrized_fixtures.Person instance at 0x2672f38>
PASSED
parametrized_fixtures.py:47: TestGreetings.test_bye[Doe] PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2672f38>

parametrized_fixtures.py:52: TestMetrics.test_name_length[John]
New Object for John : <parametrized_fixtures.Person instance at 0x26724d0>
PASSED
parametrized_fixtures.py:55: TestMetrics.test_population_count[John] PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x26724d0>

parametrized_fixtures.py:52: TestMetrics.test_name_length[Doe]
New Object for Doe : <parametrized_fixtures.Person instance at 0x2672b48>
PASSED
parametrized_fixtures.py:55: TestMetrics.test_population_count[Doe] PASSED
Finalizer called for <parametrized_fixtures.Person instance at 0x2672b48>


======================== 8 passed in 0.03 seconds ========================
Object for John is deleted.
Object for Doe is deleted.
Object for John is deleted.
Object for Doe is deleted.

I observe the same issue when using the scope value with metafunc.parametrize and setting** indirect=True** to perform setup of a test during the test execution phase instead of test collection phase. I have attached the test file that shows the problem.

Also, I tried to debug using the source code and I think after the tests are collected, the function 'parametrize_sorted' is called which changes the order.


Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions