[TIP] PyTest test directory structure confusion

Marius Gedminas marius at gedmin.as
Wed Aug 28 04:48:21 PDT 2019


Hi!

On Tue, Aug 27, 2019 at 09:45:27PM -0700, Tony Cappellini wrote:
> I’m new to using PyTest. I’ve got a very simple python file, and a very simple
> test file for it.
...

> I’ve been having a problem getting a unittest to be able to access the file
> it’s trying to test. I’m sure that I don’t have the correct directory
> configuration, but at the same time, I’m not sure what the correct directory
> configuration should be though.

You have the simplest possible case: one code file, one test file.  You
don't need a directory structure at this point, just the two files:

    mycode.py
    tests.py

inside your tests.py you can do

    import mycode

    def test_my_stuff():
        assert mycode.stuff(42) == 25   # etc.

and you should be able to run the tests with

    pytest tests.py

If you don't want to keep repeating 'tests.py' on the command line,
create a pytest.ini with

    [pytest]
    testpaths = tests.py

and then you can run just

    pytest

That's it!

I use this structure for a few of my small Python projects, e.g.
https://github.com/mgedmin/ghcloneall


> Page 25 of “Python Testing with PyTest refers to __init__.py files in the same
> directory as the test files themselves.
> 
> Under Project Structure, This website 
> [1]https://automationpanda.com/2017/03/14/python-testing-101-pytest/
> 
> states that __init__.py files should NOT be in the same directory as the test
> files.

I've no idea why that page makes this claim.  I have my tests in a
package in https://github.com/mgedmin/pyspacewar, and pytest has no
trouble with that structure.

> __init__.py files have always been the biggest headache for me, as I’ve never
> been able to find a clear reference as to what should or shouldn’t be in those
> files.
> Why is it that some are empty, while others have tons of imports?

I assume you know what they're for?  If not, see the Python Tutorial
section on packages: https://docs.python.org/3/tutorial/modules.html#packages

As for the contents, the short answer is: if you're not sure, keep
__init__.py files empty.  But if you feel like you want to put some code
in there, there likely won't be any harm.

> I would appreciate if someone could provide a reference as to how a PyTest
> project should be structured, where __init__ files should and shouldn’t be,
> along with their contents (if any).

There's more than one common source layout, and which is best is a
matter of opinion.


The one I'm used to has a 'src/' directory at the top, and puts all the
tests as subpackages inside the actual code tree.  Like, imagine if the
example I gave before grew and grew and became too big for a single
source file:

    mycode.py
    tests.py
    pytest.ini
    setup.py
    tox.ini

I've added a setup.py because you probably want to be able to pip
install the thing, and pull it any dependencies it has.  I've added a
tox.ini because tox is a wonderful tool for automating the tests -- it
creates virtualenvs for you so you get all the code and test
dependencies without polluting your global Python installation and
interfering with other projects.

(For completeness, here's what a setup.py looks:

    from setuptools import setup
    setup(
        name='mycode',
        version=...,
        author=...,
        license=...,
        url=...,
        description=...,
        long_description=...,
        py_modules=['mycode'],
    )

and here's tox.ini:

    [tox]
    envlist = py37

    [testenv]
    deps = pytest
    commands = pytest {posargs}

)

So, at this point splitting the code into several modules will result in

    src/
        mycode/
            __init__.py                    # maybe empty, maybe not
            things.py
            more_things.py
            tests/
                __init__.py                # empty file
                test_mycode.py
                test_things.py
                test_more_things.py
    pytest.ini
    setup.py
    tox.ini

Here each code module has a corresponding test module, for my own
convenience: when I want to find the unit tests for a function
mycode.things.do_x(), I'll know to look in mycode.tests.test_things
for test functions called test_do_x (with optional suffixes if there's
more than one test).

To make this work, pytest.ini changes from

    [pytest]
    testpaths = tests.py

to

    [pytest]
    testpaths = src

and you can't run 'pytest' without arguments any more (because ./src is
not in PYTHONPATH -- this is the downside of using a top-level 'src'
subdirectory), but you can run 'tox' without arguments.  tox.ini is
unchanged, setup.py changes from using

    from setuptools import setup
    setup(
        ...
        py_modules='mycode',
    )

to

    from setuptools import setup, find_packages
    setup(
        ...
        packages=find_packages('src'),
        package_dir={'': 'src'},
    )

Now, if you want to continue to write code like

    from mycode import stuff

then mycode/__init__.py will have to define the 'stuff' function, or
import and re-export it like this:

    # mycode/__init__.py
    from .things import stuff   # reexport

But if you're fine requiring that all users of mycode change their code
to do

    from mycode.things import stuff

then mycode/__init__.py can be an empty file.


Hope that helped!

Marius Gedminas
-- 
/*
 * The lockdep graph lock isn't locked while we expect it to
 * be, we're confused now, bye!
 */
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
URL: <http://lists.idyll.org/pipermail/testing-in-python/attachments/20190828/81cb52f6/attachment.pgp>


More information about the testing-in-python mailing list