Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Layout and importing for pytest in python3

I'm having trouble importing modules from my pytest functions. I know there's a million questions on this, but I've read through a bunch, and I'm still having trouble understanding.

$ tree
.
└── code
    ├── eight_puzzle.py
    ├── missionaries_and_cannibals.py
    ├── node.py
    ├── search.py
    └── test
        ├── test_eight_puzzle.py
        └── test_search.py

2 directories, 6 files
$
$ grep import code/test/test_search.py
import sys
import pytest
import code.search
$
$ pytest
...
ImportError while importing test module '~/Documents/code/test/test_search.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
code/test/test_search.py:14: in <module>
    import code.search
E   ModuleNotFoundError: No module named 'code.search'; 'code' is not a package
...

I expected that to work. 'code' is a package, right? A package in Python 3 is any directory with .py files in it.

I've also tried it with a relative import - from .. import search - and I get the following.

ImportError while importing test module '~/Documents/code/test/test_search.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
code/test/test_search.py:14: in <module>
    from .. import search
E   ImportError: attempted relative import with no known parent package

I've also tried modifying sys.path as shown here, specifying my PYTHONPATH, and adding init.py files in code and test.

Can I get this import to work without using something like setuptools? This is just for experimenting, so I'd rather not deal with the overhead.

It may also be important to note that I'm using conda, because it seems to work when I'm using the python 2 pip-installed version of pytest with init.py files.

like image 295
onlyanegg Avatar asked Oct 15 '18 01:10

onlyanegg


People also ask

Do I need to import pytest?

pytest as a testing framework needs to import test modules and conftest.py files for execution.

How do I structure a pytest file?

Project StructureThe modules containing pytests should be named “test_*. py” or “*_test.py”. While the pytest discovery mechanism can find tests anywhere, pytests must be placed into separate directories from the product code packages. These directories may either be under the project root or under the Python package.

Is pytest a Python library?

Pytest is a Python library for testing Python applications. It is an alternative to nose and unittest.


1 Answers

Some notes about directories without __init__.py files first:

Implicit namespace packages

Although a directory without an __init__.py is a valid import source in Python 3, it is not a regular package, rather being an implicit namespace package (see PEP 420 for the details). Among other properties, implicit namespace packages are second-class citizens when it comes to importing, meaning that when Python has two packages with the same name in sys.path, one being a regular package and another being an implicit namespace package, the regular one will be preferred regardless what package comes first. Check it yourself:

$ mkdir -p implicit_namespace/mypkg
$ echo -e "def spam():\n    print('spam from implicit namespace package')" > implicit_namespace/mypkg/mymod.py
$ mkdir -p regular/mypkg
$ touch regular/mypkg/__init__.py
$ echo -e "def spam():\n    print('spam from regular package')" > regular/mypkg/mymod.py
$ PYTHONPATH=implicit_namespace:regular python3 -c "from mypkg.mymod import spam; spam()"

This will print spam from regular package: although implicit_namespace comes first in sys.path, mypkg.mymod from regular is imported instead because regular/mypkg is a regular package.


Now you know that since your package code is an implicit namespace package, Python will prefer regular imports of code to yours if it encounters one. Unfortunately for you, there is a module code in the stdlib, so it's practically a "reverse name shadowing" problem: you have an import object with the same name as the one from stdlib, but instead of shadowing the stdlib import, it shadows yours.

You thus need to do two things in order to make your layout usable:

  1. give the code dir a unique name (let it be mycode for this answer's example)
  2. after that, you still need to fix the sys.path when running the tests from the project root dir because it's not in sys.path per se. You have some possibilities:
    • add an empty conftest.py file to the root dir (aside the mycode dir). This will instruct pytest to add the root dir to sys.path (see here for an explanation). You can now just run pytest as usual and the imports will be resolved;
    • run the tests via python -m pytest - invoking interpreter directly adds the current dir to sys.path;
    • add the current dir to sys.path via PYTHONPATH env var, e.g. run PYTHONPATH=. pytest.
like image 75
hoefling Avatar answered Sep 28 '22 07:09

hoefling