Skip to content

Attempt to clarify the confusion regarding __init__ files and unique test names #2297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 85 additions & 46 deletions doc/en/goodpractices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,68 +30,106 @@ Within Python modules, ``pytest`` also discovers tests using the standard


Choosing a test layout / import rules
------------------------------------------
-------------------------------------

``pytest`` supports two common test layouts:

* putting tests into an extra directory outside your actual application
code, useful if you have many functional tests or for other reasons
want to keep tests separate from actual application code (often a good
idea)::
Tests outside application code
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

setup.py # your setuptools Python package metadata
Putting tests into an extra directory outside your actual application code
might be useful if you have many functional tests or for other reasons want
to keep tests separate from actual application code (often a good idea)::

setup.py
mypkg/
__init__.py
appmodule.py
app.py
view.py
tests/
test_app.py
test_view.py
...

This way your tests can run easily against an installed version
of ``mypkg``.

Note that using this scheme 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``.

* inlining test directories into your application package, useful if you
have direct relation between (unit-)test and application modules and
want to distribute your tests along with your application::
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::

setup.py # your setuptools Python package metadata
setup.py
mypkg/
__init__.py
appmodule.py
...
test/
test_app.py
...
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section became more complicated than I hoped for. I'm considering just recommending straight away to add __init__.py files except to the tests directory, which seems to solve all issues discussed here. This would greatly simplify this section.

@ionelmc @philtay thoughts?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work. pytest uses the full directory path as import name. You can choose between using __init__.py files or not, but if you do then you have to put them everywhere or it'll break.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this section is complex (not complicated), because this is a complex problem with different solutions. I suggest to document all of them as it's hard to figure out these things (probably impossible for a newbie).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if you do then you have to put them everywhere or it'll break.

Are you certain? I just tested this and it works:

.tmp/
    tests/
        foo/
            __init__.py
            test_foo.py
        bar/
            __init__.py
            test_foo.py

Executing pytest from .tmp works as expected, with only .tmp/tests being added to sys.path.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try this:

.tmp/
    tests/
        foo/
            __init__.py
            test_foo.py
        email/
            __init__.py
            test_email.py

pytest will import email.test_email, but ooops email is a stdlib package...
The same goes with an installed package. It will try to import mypkg.test_xxx but there isn't a test_xxx.py in your installed mypkg.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestions, let me know what you think.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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.

In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
sub-directory of your root::

setup.py
src/
mypkg/
__init__.py
app.py
view.py
tests/
__init__.py
foo/
__init__.py
test_view.py
bar/
__init__.py
test_view.py

Important notes relating to both schemes:

- **make sure that "mypkg" is importable**, for example by typing once::
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ș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.

pip install -e . # install package using setup.py in editable mode
Tests as part of application code
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

- **avoid "__init__.py" files in your test directories**.
This way your tests can run easily against an installed version
of ``mypkg``, independently from the installed package if it contains
the tests or not.
Inlining test directories into your application package
is useful if you have direct relation between tests and application modules and
want to distribute them along with your application::

- With inlined tests you might put ``__init__.py`` into test
directories and make them installable as part of your application.
Using the ``pytest --pyargs mypkg`` invocation pytest will
discover where mypkg is installed and collect tests from there.
With the "external" test you can still distribute tests but they
will not be installed or become importable.
setup.py
mypkg/
__init__.py
app.py
view.py
test/
__init__.py
test_app.py
test_view.py
...

In this scheme, it is easy to your run tests using the ``--pyargs`` option::

Typically you can run tests by pointing to test directories or modules::
pytest --pyargs mypkg

pytest tests/test_app.py # for external test dirs
pytest mypkg/test/test_app.py # for inlined test dirs
pytest mypkg # run tests in all below test directories
pytest # run all tests below current dir
...
``pytest`` will discover where ``mypkg`` is installed and collect tests from there.

Note that this layout also works in conjunction with the ``src`` layout mentioned in the previous section.

Because of the above ``editable install`` mode you can change your
source code (both tests and the app) and rerun tests at will.
Once you are done with your work, you can `use tox`_ to make sure
that the package is really correct and tests pass in all
required configurations.

.. note::

Expand Down Expand Up @@ -144,7 +182,13 @@ for installing your application and any dependencies
as well as the ``pytest`` package itself. This ensures your code and
dependencies are isolated from the system Python installation.

If you frequently release code and want to make sure that your actual
You can then install your package in "editable" mode::

pip install -e .

which lets you change your source code (both tests and application) and rerun tests at will.

Once you are done with your work and want to make sure that your actual
package passes all tests you may want to look into `tox`_, the
virtualenv test automation tool and its `pytest support
<https://tox.readthedocs.io/en/latest/example/pytest.html>`_.
Expand All @@ -154,11 +198,6 @@ options. It will run tests against the installed package and not
against your source code checkout, helping to detect packaging
glitches.

Continuous integration services such as Jenkins_ can make use of the
``--junitxml=PATH`` option to create a JUnitXML file and generate reports (e.g.
by publishing the results in a nice format with the `Jenkins xUnit Plugin
<https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin>`_).


Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
--------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ commands=
pytest -ra {posargs:testing/test_unittest.py}

[testenv:docs]
skipsdist=True
usedevelop=True
basepython=python
changedir=doc/en
deps=
Expand Down