-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
This inspired by a discussion: #12748
What's the problem this feature will solve?
We have a project that's about a different kind of test. We have a lot of classes with "Test" in the name, including some that start with "Test". We would also like to organize some of our pytest tests in classes whose names start with "Test". We distinguish them by directory; all our pytest tests are in tests
, and all our production code is in src
. We'd like an easy way to set this up with no distortion to our production code and minimal shenanigans in the test code, while getting no warnings.
Naively one might think that limiting discovery to a particular directory, as with pytest tests
or testpaths=["tests"]
. However, if a production file called TestFoo is imported from production code into test code, it will still be discovered, resulting in a warning like:
src/pytest_collection_issue/domain.py:6
src/pytest_collection_issue/domain.py:6: PytestCollectionWarning: cannot collect test class 'Testament' because it has a __init__ constructor (from: tests/test_domain.py)
class Testament(object):
Describe the solution you'd like
For me, the best solution is just to by default disable discovery beyond the requested testpaths
. Trying to run something just because it's included from wherever strikes me as counterintuitive behavior. If somebody needs that behavior, perhaps an option like discover_imports
would be useful.
The second-best solution would be an option where we can specify paths to definitely not discover things in. E.g.:
testpaths = ["tests"]
excludepaths = ["src"]
Alternative Solutions
There are many ways to solve this, including manually adding a __test__ = false
to each production class, using a marker mixin like NotAPytestTest to achieve the same end, importing production classes into the tests under different names, doing only local imports, or renaming our test classes. All of these are a little clunky, and require people to remember a ritual under certain circumstances.
The solution I went with is to put
for a_class in [i[1] for i in (globals().items()) if inspect.isclass(i[1])]:
if a_class.__name__.startswith('Test'):
a_class.__test__ = False
after my domain imports and before my test classes. It still seems clunky to me, but only has to happen once per file of tests. I think it would be better still if this could be fixed in one spot, or have it work by default.
Additional context
Please see this GitHub discussion and this example repository that demonstrates the problem.