91

I have following unittest code running via py.test. Mere presence of the constructor make the entire class skip when running py.test -v -s

collected 0 items / 1 skipped

Can anyone please explain to me this behaviour of py.test?

I am interested in understanding py.test behaviour, I know the constructor is not needed.

Thanks, Zdenek

class TestClassName(object):
    def __init__(self):
       pass

    def setup_method(self, method):
       print "setup_method called"

    def teardown_method(self, method):
       print "teardown_method called"

    def test_a(self):
       print "test_a called"
       assert 1 == 1

    def test_b(self):
       print "test_b called"
       assert 1 == 1
Zdenek Maxa
  • 1,319
  • 1
  • 10
  • 12

4 Answers4

73

The documentation for py.test says that py.test implements the following standard test discovery:

  • collection starts from the initial command line arguments which may be directories, filenames or test ids. recurse into directories, unless they match norecursedirs
  • test_*.py or *_test.py files, imported by their package name.
  • Test prefixed test classes (without an __init__ method) [<-- notice this one here]
  • test_ prefixed test functions or methods are test items

So it's not that the constructor isn't needed, py.test just ignores classes that have a constructor. There is also a guide for changing the standard test discovery.

Jerry101
  • 12,157
  • 5
  • 44
  • 63
Matti Lyra
  • 12,828
  • 8
  • 49
  • 67
  • Right. I should have been reading more carefully. Thanks a lot for spotting it. – Zdenek Maxa Jan 29 '14 at 12:38
  • The link was fixed. – joon Jun 16 '16 at 20:24
  • [This](http://doc.pytest.org/en/latest/goodpractices.html#conventions-for-python-test-discovery) is the updated link for the Conventions for Python test discovery, and [this](http://doc.pytest.org/en/latest/example/pythoncollection.html) is the updated link for the guide. Both are working as for September 2019. – Angelo Cardellicchio Sep 19 '19 at 08:29
  • 2
    But could you discover classes with __new__ or __init__, if you change discovery rules? Documentation on TestClass discovery is very short, and only mentions a couple concrete cases. – Alexandr Crit Jun 29 '22 at 08:32
  • Note that if you *want* some sort of initialization for every test in a class, an autouse fixture inside the class is a good way to accomplish that without having to mess with custom test discovery (and it will have all the other features of fixtures, e.g. it can use other fixtures, be scoped, or be parameterized) – Dominick Pastore Jan 22 '23 at 18:03
56

As already mentioned in the answer by Matti Lyra py.test purposely skips classes which have a constructor. The reason for this is that classes are only used for structural reasons in py.test and do not have any inherent behaviour, while when actually writing code it is the opposite and much rarer to not have an .__init__() method for a class. So in practice skipping a class with a constructor will likely be what was desired, usually it is just a class which happens to have a conflicting name.

Lastly py.test needs to instantiate the class in order to execute the tests. If the constructor takes any arguments it can't instantiate it, so again skipping is the right thing to do.

Matti Lyra
  • 12,828
  • 8
  • 49
  • 67
flub
  • 5,953
  • 27
  • 24
  • 9
    I agree but i think in the future should give a warning (once pytest grows a little warning mechanism). – hpk42 Jan 30 '14 at 09:45
  • Hi Holger, indeed, that would be nice. I only discovered that the class was shielded by running module by module from a larger test suite. An explicit warning would tell immediately. Other than that, let me repeat what I told you back in 2010 at Europython - thanks for a great testing framework! – Zdenek Maxa Jan 30 '14 at 11:37
  • IIRC if you invoke py.test with -rs you will already see the class with `__init__()` as skipped. – flub Jan 30 '14 at 16:36
  • 8
    If you did want to initialize something in the class before the rest of the tests in the class you can use a pytest fixture. i.e. `@pytest.fixture(autouse=True) def _setup(self): self.something = something` – sam Oct 26 '20 at 16:15
  • 4
    It appears pytest finally grew that warning mechanism! `PytestCollectionWarning: cannot collect test class 'TestClass' because it has a __init__ constructor (from: test/test_classes.py)`. Searching with that warning led me here... :). (I'm using pytest 5.3.5) – LHM May 06 '21 at 21:35
1

All the above answers clearly explain the underlying cause, I just thought to share my experience and workaround the warnings.

I got my test to work without the warnings by aliasing the imported Class

from app.core.utils import model_from_meta
from app.core.models import Panel, TestType as _TestType
from app.core.serializers import PanelSerializer, TestType as _TestTypeSerializer


def test_model_from_meta():
    assert (Panel is model_from_meta(PanelSerializer))
    assert (_TestType is model_from_meta(_TestTypeSerializer))

After importing the class using aliases the warnings no longer get printed

I hope this helps someone.

Tega Ukavwe
  • 99
  • 1
  • 4
0

In my case, I just so happened to have a parameter's class names TestParams, which conflicts with pytest looking for classes beginning with the name test....

Solution: rename your own class

Source

DanielBell99
  • 896
  • 5
  • 25
  • 57