From 6c60840aaea59832095196f313ea70cb144b099d Mon Sep 17 00:00:00 2001 From: Philipp A Date: Fri, 19 Aug 2022 14:47:23 +0200 Subject: [PATCH] First attempt to overhaul goodpractices.rst --- doc/en/conf.py | 1 + doc/en/explanation/goodpractices.rst | 133 +++++++++++++-------------- 2 files changed, 65 insertions(+), 69 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index b0c158e5a..5184ee7b1 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -393,6 +393,7 @@ intersphinx_mapping = { "tox": ("https://tox.wiki/en/stable", None), "virtualenv": ("https://virtualenv.pypa.io/en/stable", None), "setuptools": ("https://setuptools.pypa.io/en/stable", None), + "packaging": ("https://packaging.python.org/en/latest", None), } diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 0b812ba58..c027b61d0 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -12,41 +12,26 @@ For development, we recommend you use :mod:`venv` for virtual environments and as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from your system Python installation. -Next, place a ``pyproject.toml`` file in the root of your package: +Create a ``pyproject.toml`` file in the root of your repository as described in +:doc:`packaging:tutorials/packaging-projects`. +The first few lines should look like this: .. code-block:: toml [build-system] - requires = ["setuptools>=42", "wheel"] - build-backend = "setuptools.build_meta" - -and a ``setup.cfg`` file containing your package's metadata with the following minimum content: - -.. code-block:: ini + requires = ["hatchling"] + build-backend = "hatchling.build" [metadata] - name = PACKAGENAME - - [options] - packages = find: + name = "PACKAGENAME" where ``PACKAGENAME`` is the name of your package. -.. note:: - - If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file: - - .. code-block:: python - - from setuptools import setup - - setup() - You can then install your package in "editable" mode by running from the same directory: .. code-block:: bash - pip install -e . + pip install -e . which lets you change your source code (both tests and application) and rerun tests at will. @@ -112,43 +97,24 @@ This has the following benefits: See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and ``python -m pytest``. -Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode ` -(which is the default): your test files must have **unique names**, because -``pytest`` will import them as *top-level* modules since there are no packages -to derive a full package name from. In other words, the test files in the example above will -be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to -``sys.path``. +For the most convenient experience, +choose the ``import-lib`` :ref:`import mode `. +To this end, add the following to your ``pyproject.toml``: -If you need to have test modules with the same name, you might add ``__init__.py`` files to your -``tests`` folder and subfolders, changing them to packages: +.. code-block:: toml -.. code-block:: text + [tool.pytest.ini_options] + addopts = [ + "--import-mode=importlib", + ] - pyproject.toml - setup.cfg - mypkg/ - ... - tests/ - __init__.py - foo/ - __init__.py - test_view.py - bar/ - __init__.py - test_view.py - -Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing -you to have modules with the same name. But now this introduces a subtle problem: in order to load -the test modules from the ``tests`` directory, pytest prepends the root of the repository to -``sys.path``, which adds the side-effect that now ``mypkg`` is also importable. - -This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment, -because you want to test the *installed* version of your package, not the local code from the repository. +The default :ref:`import mode ` ``prepend`` has several drawbacks [1]_. .. _`src-layout`: -In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a -sub-directory of your root: +Generally, but especially if you use the default import mode, +it is **strongly** suggested to use a ``src`` layout. +Here, your application root package resides in a sub-directory of your root: .. code-block:: text @@ -159,26 +125,11 @@ sub-directory of your root: __init__.py app.py view.py - tests/ - __init__.py - foo/ - __init__.py - test_view.py - bar/ - __init__.py - test_view.py - + tests/... This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent `blog post by Ionel Cristian Mărieș `_. -.. note:: - The ``--import-mode=importlib`` option (see :ref:`import-modes`) does not have - any of the drawbacks above because ``sys.path`` is not changed when importing - test modules, so users that run into this issue are strongly encouraged to try it. - - The ``src`` directory layout is still strongly recommended however. - Tests as part of application code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -285,3 +236,47 @@ See also `pypa/setuptools#1684 ` setuptools intends to `remove the test command `_. + + +.. [1] The default ``prepend`` :ref:`import mode ` works as follows: + + Since there are no packages to derive a full package name from, + ``pytest`` will import your test files as *top-level* modules. + The test files in the first example would be imported as + ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``. + + This results in the following drawback compared to the import mode ``import-lib``: + your test files must have **unique names**. + + If you need to have test modules with the same name, + as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders, + changing them to packages: + + .. code-block:: text + + pyproject.toml + setup.cfg + mypkg/ + ... + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + + Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, + allowing you to have modules with the same name. + But now this introduces a subtle problem: + in order to load the test modules from the ``tests`` directory, + pytest prepends the root of the repository to ``sys.path``, + which adds the side-effect that now ``mypkg`` is also importable. + + This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment, + because you want to test the *installed* version of your package, + not the local code from the repository. + + The ``importlib`` import mode does not have any of the drawbacks above, + because ``sys.path`` is not changed when importing test modules.