My pytest setup is running very slow, especially during collection phase.
So I have put up a pytest setup for my Django project, and each Django app's test-files are located in its own folder, i.e., the tree looks as follows
root
|
+--a
| |
| +--tests
| |
| +--conftest.py
| +--testAa.py
| +--testBa.py
+--b
| |
| +--tests
| |
| +--conftest.py
| +--testAb.py
| +--testBb.py
...
pytest.ini
The pytest.ini file specifies where to look for the tests and has the following content
[pytest]
DJANGO_SETTINGS_MODULE = project.project.settings.test
python_files = tests.py test_*.py *_tests.py
addopts = --reuse-db
For each app within the tests
folder I have a file called contest.py
. This file creates a set of objects that are used multiple times in the test files. For instance if an object of class A
is used more than once, a contest creates that variable once and the tests use this conftest as input. Each conftest has the decorator @pytest.fixture(scope="function")
, and the tests have the decorator @pytest.mark.django_db
.
I don't think the loading times are caused by the conftests, or the decorators discussed in the last paragraph, but rather the tree structure and pytest.ini
file I have put up. Are there any rules for what is a good structure for this? As I said, the loading times are extremely high for collecting the tests. To be more accurate, I have about 80 tests, and collecting them takes about 40 seconds. Running them all takes an additional 20.
When you invoke pytest , it will scan every subdirectory in project root, looking for tests. This may slow down the test collection; it may be wise to exclude unrelated directories from being scanned.
Using the norecursedirs option in pytest. ini or tox. ini can save a lot of collection time, depending on what other files you have in your working directory. My collection time is roughly halved for a suite of 300 tests when I have that in place (0.34s vs 0.64s).
pytest has the option -x or --exitfirst which stops the execution of the tests instanly on first error or failed test. pytest also has the option --maxfail=num in which num indicates the number of errors or failures required to stop the execution of the tests.
The conftest.py file serves as a means of providing fixtures for an entire directory. Fixtures defined in a conftest.py can be used by any test in that package without needing to import them (pytest will automatically discover them).
E.g. pytest --collect-only root/a/tests. Maybe you have some large dir in project root which slows down the scan. If this is the case, you can restrict the directories via norecursedirs or testpaths options. It runs in 0.03 seconds in that case. I will try your second comment tomorrow, but it's getting late now. Thanks :)
More generally, pytest follows standard test discovery rules. Pytest supports several ways to run and select tests from the command-line. This will run tests which contain names that match the given string expression (case-insensitive), which can include Python operators that use filenames, class names and function names as variables.
Check that you do not have own code which interact with pytest collection phase and make it slower. If not, you can run profiler like line profiler to see which code is slow. What code would interact with collection phase? How long does collection take in your setup?
By default, pytest will not show test durations that are too small (<0.005s) unless -vv is passed on the command-line. You can early-load plugins (internal and external) explicitly in the command-line with the -p option:
For me the issue was with a particular conftest.py
taking a very long time to run (it was setting up some huge fixtures for a different test that I didn't need here).
This was tricky to diagnose, since the delay still occured with --collect-only
flag and custom rootdir
set to the test directory only (the conftest was in a different directory far away but was still being detected somehow).
The solution for me was to run pytest --noconftest
.
More or less restating my other answer:
When you invoke pytest
, it will scan every subdirectory in project root, looking for tests. This may slow down the test collection; it may be wise to exclude unrelated directories from being scanned. pytest
offers two configuration options for that:
norecursedirs
- holds directories that will be excluded from scanning. Use this option when you are looking for the pattern "include all, exclude selected". By default, norecursedirs
is set to '.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'
, so beware that when you override this option, the defaults are gone and you have to add them back.testpaths
- holds directories that should only be considered for the scan, so this is basically the opposite to what norecursedirs
is doing. Use this option when looking for the pattern "exclude all, include selected". This option also adds some minor or more significant speedup to the test discovery, depending on what you keep in the project root - most of the sudbirectories won't be traversed at all and the tests run starts sooner.Usage: either place the options in the pytest.ini
/setup.cfg
/tox.ini
:
[tool:pytest]
testpaths = tests othertests doc
or pass them via --override-ini
from command line.
pytest -o "testpaths=tests othertests doc" ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With