Skip to content

Commit

Permalink
Use pytest as the test runner
Browse files Browse the repository at this point in the history
Pytest has several benefits over unittest:
- captures stdout/stderr, restoring the ability to get an overview of
  the test run progress. To further improve the test run overview, the
  runner verbosity was decreased.
- shows detailed reports for failures and errors

The invocation is also much easier. Before this change:

```
DJANGO_SETTINGS_MODULE=config.settings.test django-admin --no-input --keepdb --parallel
```

With pytest (and a pytest.ini configuration), invocation is simply:
```
pytest
```

For more on pytest, see https://pytest-django.readthedocs.io/en/latest/index.html#why-would-i-use-this-instead-of-django-s-manage-py-test-command

The `--failfast` option was not ported. It’s viewed as a hindrance by
most team members, having to run the CI multiple times to get a full
test report.

With the simplified invocation, the `make test-interactive` recipe was
no longer useful and was dropped.

Also, passing arguments to the test runner (such as `-k TestFilter`) is
also eased compared to the previous make TARGET variable.

With make:
```
make test TARGET="-k TestFilter"
```

With pytest:
```
pytest -k TestFilter
```

Pytests parallel test runner splits the execution at the individual test
level, whereas Django runner (based on unittest) splits at the TestCase
level. That caused an issue with ImportSiaeManagementCommandsTest, which
setup a test directory that would be shared by pytest workers. Update
the test suite to use a temporary directory, specific to the worker
process.

Finally, add pytest-xdist as a dependency to support running the test
suite in parallel.
  • Loading branch information
francoisfreitag committed Sep 29, 2022
1 parent 2fb036e commit 3260fbb
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 18 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Expand Up @@ -61,6 +61,7 @@ jobs:
- name: 🚧 Check pending migrations
run: django-admin makemigrations --check --dry-run --noinput
- name: 🤹‍ Django tests
run: django-admin test --noinput --failfast --parallel
run: make test
env:
DJANGO_DEBUG: True
USE_VENV: 1
14 changes: 4 additions & 10 deletions Makefile
Expand Up @@ -112,19 +112,13 @@ graph_models_itou:
# Tests.
# =============================================================================

.PHONY: test test-interactive coverage
.PHONY: coverage test

TEST_OPTS += --force-color --timing --no-input

test:
$(EXEC_CMD) ./manage.py test --settings=config.settings.test $(TEST_OPTS) --parallel $(TARGET)

# Lets you add a debugger.
test-interactive:
$(EXEC_CMD) ./manage.py test --settings=config.settings.test $(TEST_OPTS) --keepdb $(TARGET)
test: $(VIRTUAL_ENV)
$(EXEC_CMD) pytest $(TARGET)

coverage:
$(EXEC_CMD) coverage run ./manage.py test itou --settings=config.settings.test --no-input
$(EXEC_CMD) coverage run -m pytest
$(EXEC_CMD) coverage html

# Docker shell.
Expand Down
16 changes: 11 additions & 5 deletions itou/siaes/tests/tests_import_siae_command.py
Expand Up @@ -2,8 +2,10 @@
import importlib
import os
import shutil
import tempfile
import unittest
from pathlib import Path
from unittest import mock

from django.conf import settings
from django.core import mail
Expand Down Expand Up @@ -42,7 +44,6 @@ def lazy_import_siae_command():
)
class ImportSiaeManagementCommandsTest(TransactionTestCase):

path_dest = "./siaes/management/commands/data"
path_source = "./siaes/fixtures"
app_dir_path = Path((settings.APPS_DIR))
mod = None
Expand All @@ -52,17 +53,22 @@ def setUpClass(cls):
"""We need to setup fake files before loading any `import_siae` related script,
since it does rely on dynamic file loading upon startup (!)
"""
# copying datasets from fixtures dir
path_dest = tempfile.mkdtemp()
cls.addClassCleanup(shutil.rmtree, path_dest)
data_dir = Path(path_dest) / "data"
data_dir.mkdir()
data_dir_mock = mock.patch("itou.siaes.management.commands._import_siae.utils.CURRENT_DIR", data_dir)
data_dir_mock.start()
cls.addClassCleanup(data_dir_mock.stop)

files = [x for x in cls.app_dir_path.joinpath(cls.path_source).glob("fluxIAE_*.csv.gz") if x.is_file()]
cls.app_dir_path.joinpath(cls.path_dest).mkdir(parents=True, exist_ok=True)
for file in files:
shutil.copy(file, cls.app_dir_path.joinpath(cls.path_dest))
shutil.copy(file, data_dir)

cls.mod = importlib.import_module("itou.siaes.management.commands._import_siae.convention")

@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.app_dir_path.joinpath(cls.path_dest))
cls.mod = None

def test_uncreatable_conventions_for_active_siae_with_active_convention(self):
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
@@ -1,4 +1,4 @@
[pytest]
DJANGO_SETTINGS_MODULE = config.settings.test
python_files = test*.py
addopts = --reuse-db --verbose --capture=no
addopts = --numprocesses=logical --reuse-db
1 change: 1 addition & 0 deletions requirements/dev.in
Expand Up @@ -39,6 +39,7 @@ requests-mock==1.9.3 # https://github.com/jamielennox/requests-mock
respx==0.19.2 # https://lundberg.github.io/respx/
pytest==7.1.2 # https://github.com/pytest-dev/pytest
pytest-django== 4.5.2 # https://github.com/pytest-dev/pytest-django/
pytest-xdist # https://pypi.org/project/pytest-xdist/

# Data extracts
# ------------------------------------------------------------------------------
Expand Down
18 changes: 17 additions & 1 deletion requirements/dev.txt
Expand Up @@ -365,6 +365,10 @@ et-xmlfile==1.1.0 \
--hash=sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c \
--hash=sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada
# via openpyxl
execnet==1.9.0 \
--hash=sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5 \
--hash=sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142
# via pytest-xdist
executing==1.0.0 \
--hash=sha256:550d581b497228b572235e633599133eeee67073c65914ca346100ad56775349 \
--hash=sha256:98daefa9d1916a4f0d944880d5aeaf079e05585689bebd9ff9b32e31dd5e1017
Expand Down Expand Up @@ -897,7 +901,9 @@ pure-eval==0.2.2 \
py==1.11.0 \
--hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \
--hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378
# via pytest
# via
# pytest
# pytest-forked
pycodestyle==2.8.0 \
--hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \
--hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f
Expand Down Expand Up @@ -983,10 +989,20 @@ pytest==7.1.2 \
# via
# -r requirements/dev.in
# pytest-django
# pytest-forked
# pytest-xdist
pytest-django==4.5.2 \
--hash=sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e \
--hash=sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2
# via -r requirements/dev.in
pytest-forked==1.4.0 \
--hash=sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e \
--hash=sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8
# via pytest-xdist
pytest-xdist==2.5.0 \
--hash=sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf \
--hash=sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65
# via -r requirements/dev.in
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
--hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
Expand Down

0 comments on commit 3260fbb

Please sign in to comment.