102

I want my tests folder separate to my application code. My project structure is like so

myproject/
  myproject/
    myproject.py
    moduleone.py
  tests/
    myproject_test.py

myproject.py

from moduleone import ModuleOne

class MyProject(object)
....

myproject_test.py

from myproject.myproject import MyProject
import pytest

...

I use myproject.myproject since I use the command

python -m pytest

from the project root directory ./myproject/

However, then the imports within those modules fail with

E ModuleNotFoundError: No module named 'moduleone'

I am running Python 3.7 and have read that since 3.3, empty __init__ files are no longer needed which means my project becomes an implicit namespace package

However, I have tried adding an __init__.py file in myproject/myproject/ and also tried adding a conftest.py file in myproject/ but neither works

I have read answers that say to mess with the paths and then upvoted comments in other questions saying not to.

What is the correct way and what am I missing?

EDIT;

Possibly related, I used a requirements.txt to install pytest using pip. Could this be related? And if so, what is the correct way to install pytest in this case?

EDIT 2:

One of the paths in sys.path is /usr/src/app/ which is a docker volume lined to /my/local/path/myproject/.

Should the volume be /my/local/path/myproject/myproject/ instead?

7
  • 1
    Honestly, this is going to be a opinion war if anything. You could move the test folder into your main structure and execute it from there, and your could would work as-is. Wouldn't need to change a thing since the import paths would be relevant. Other than that, you would need to monkey-patch the path in the test-files to include the target directory, and it wouldn't be a bad thing (opinion). Just know why and what you're doing.
    – Torxed
    Commented Feb 26, 2019 at 22:21
  • 3
    Long comments.. You could also (on your command line) append the PYTHONPATH to include the target directory. Meaning you wouldn't have to manipulate sys.path from any of your scripts, but you would get a updated path upon running the test. PYTHONPATH=./myproject python -m pytest as if you did.
    – Torxed
    Commented Feb 26, 2019 at 22:23
  • Of the directories you listed in the project structure, which (if any) are in your PYTHONPATH? Commented Feb 27, 2019 at 0:01
  • @JohnGordon updated the question with new info
    – myol
    Commented Feb 27, 2019 at 10:05
  • 1
    have you tried adding the empty init.py to the ./tests directory instead of your project directory. It might sound strange, but worth trying.
    – cli
    Commented May 26, 2020 at 23:37

18 Answers 18

93

Not sure if this solution was specific to my problem, but I simply add __init__.py to my tests folder and that solved the problem.

4
  • 14
    This does the trick, thanks. May I know why it changes pytest import behavior by adding a init.py in tests folder?
    – hui chen
    Commented May 24, 2021 at 7:40
  • worked for me too, however is this bad practise? Commented Mar 2, 2022 at 16:21
  • 27
    My tests folder already had an __init__.py file, so I can confirm that this solution is not universal.
    – muad-dweeb
    Commented Mar 4, 2022 at 21:04
  • @huichen __init__.py tells Python that the folder is a module. Without it, the folder is not a module and so Python cannot find its name when used in import statements.
    – theberzi
    Commented Sep 6, 2022 at 7:35
63

Solution: use the PYTHONPATH env. var

PYTHONPATH=. pytest

As mentioned by @J_H, you need to explicitly add the root directory of your project, since pytest only adds to sys.path directories where test files are (which is why @Mak2006's answer worked.)


Good practice: use a Makefile or some other automation tool

If you do not want to type that long command all the time, one option is to create a Makefile in your project's root dir with, e.g., the following:

.PHONY: test
test:
    PYTHONPATH=. pytest

Which allows you to simply run:

make test

Another common alternative is to use some standard testing tool, such as tox.

4
  • 5
    Just a minor note: makefiles require tabs for indentation, so copy pasting your code might not work since it uses spaces.
    – Silver
    Commented Aug 8, 2021 at 4:21
  • Thanks @SilverSlash! The source code actually uses tabs, but, indeed, it is rendered as spaces in the answer. Does anyone know how to fix this? Commented Aug 9, 2021 at 12:56
  • I think that's a limitation of HTML and web browsers, unfortunately. Commented Nov 10, 2021 at 19:01
  • 1
    Halleluja. WHY? <insert meme here>
    – knittl
    Commented Oct 25, 2022 at 21:24
20

Be sure to include . dot in the $PYTHONPATH env var.

Use $ python -m site, or this code fragment to debug such issues:

import pprint
import sys
pprint.pprint(sys.path)

Your question managed to use myproject at three different levels. At least during debugging you might want to use three distinct names, to reduce possible confusion.

19

In my case I added a __init__.py to my test directory with this inside it:

import sys
sys.path.append('.')

My app code is at the same level as my test directory.

3
  • 2
    Adding this fixed my issue.
    – Zaxxon
    Commented Apr 24, 2022 at 11:54
  • 1
    I added an empty init.py file and it worked as well.
    – mapazarr
    Commented May 10, 2023 at 12:13
  • This fixed my issue. I moved tests to the same level as my app code. Then, I made an __init__.py file with the code described above. Finally, my pytest.ini file was empty too and I was able to run pytest successfully.
    – blastoise
    Commented Dec 2, 2023 at 1:06
18

In 2023.02, according to the document of pytest, you can simply add following config to your pyproject.toml to solve this problem

[tool.pytest.ini_options]
pythonpath = "src"
addopts = [
    "--import-mode=importlib",
]
2
  • This is the way to go these days. This answer should be either accepted as the ideal answer or voted higher up. Also heads up, the value of pythonpath will be the "root" of the project and not necessarily src (unless src itself is the root for your project).
    – Jarmos
    Commented Mar 13 at 11:27
  • 1
    It worked for me with pythonpath = "." Commented Apr 3 at 20:54
10

In my case it is because I installed pytest on the system level but not in my virtual environment.

You can test this by python -m pytest. If you see ModuleNotFoundError: No module named 'pytest' then your pytest is at the system level.

Install pytest when the virtual environment is activated will fix this.

2
  • 3
    In my case, it's installed on both system and virtualenv level. But only python3 -m pytest works.
    – naisanza
    Commented Jan 16, 2022 at 5:00
  • This was the issue with my system. Thank you :)
    – lumos42
    Commented Jul 13, 2023 at 10:45
8

Better Solution

Try adding a single __init__.py to your tests directory (a level up from your module) with this contents:

import sys
sys.path.append('.')
sys.path.append('./my_module')

Your file structure should look like this:

project
  my_module
    package.py
  tests
    __init__.py
    my_tests.py

The first append to sys.path will enable you to import from <your-module-name> and the second will enable your packages to import as normal.

In your tests you can import by using from my_module.package import function whereas in your module import using simply from package import function.


Edit: Seems like this solution is not universal (like the others).

I was able to solve this issue using help from this answer. Add an __init__.py to your main module directory that contains

import pathlib, sys
sys.path.append(str(pathlib.Path(__file__).parent))

I also added another __init__.py to my tests directory (thanks to this answer) with

import sys
sys.path.append('.')
2
  • 1
    Thanks a lot. Had a similar issue. Solved my problem Commented Feb 7, 2023 at 15:39
  • This worked great for me and it is simple.
    – Kent Bull
    Commented Mar 7, 2023 at 1:19
5

Kept everything same and just added a blank test file at the root folder .. Solved

Here are the findings, this problem really bugged me for a while. My folder structure was

mathapp/
    - server.py  
    - configuration.py 
    - __init__.py 
    - static/ 
       - home.html  
tests/            
    - functional 
       - test_errors.py 
    - unit  
       - test_add.py

and pytest would complain with the ModuleNotFoundError.

I introduced a mock test file at the same level as mathsapp and tests directory. The file contained nothing. Now pytest does not complain.

Result without the file

$ pytest
============================= test session starts =============================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\mak2006\workspace\0github\python-rest-app-cont
collected 1 item / 1 error

=================================== ERRORS ====================================
_______________ ERROR collecting tests/functional/test_func.py ________________
ImportError while importing test module 'C:\mainak\workspace\0github\python-rest-app-cont\tests\functional\test_func.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests\functional\test_func.py:4: in <module>
    from mathapp.service import sum
E   ModuleNotFoundError: No module named 'mathapp'
=========================== short test summary info ===========================
ERROR tests/functional/test_func.py
!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
============================== 1 error in 0.24s ===============================

Results with the file

$ pytest
============================= test session starts =============================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\mak2006\workspace\0github\python-rest-app-cont
collected 2 items

tests\functional\test_func.py .                                          [ 50%]
tests\unit\test_unit.py .                                                [100%]

============================== 2 passed in 0.11s ==============================
0
2

So it seems that the sys.path has to include the application directory rather than the project root folder containing the application directory and test directory.

So in my case /my/local/path/myproject/myproject/ had to be in sys.path rather than /my/local/path/myproject/.

Then I could run pytest in /my/local/path/myproject/ (didn't need python -m pytest). This meant that the modules within /myproject/myproject/ could find each other and the tests as well without any namespace nesting.

So my tests looked like

 from moduleone import ModuleOne
 import pytest

 def test_fun():
     assert ModuleOne.example_func() == True

That said, there seem to be many gotchas, so I have no idea if this is correct..

2

I suggest you have a code structure like this:

myproject/
  helpers/
    moduleone.py
    moduletwo.py
  tests/
    myproject_test.py
  conftest.py

And the content of conftest.py file is:

pytest_plugins = ['helpers']

Run pytest again.

2
  • 1
    what is the purpose of pytest_plugins = ['helpers']
    – Vad1mo
    Commented Apr 12, 2022 at 22:23
  • Replacing helpers with the name of your module. Pytest will read this line and add the your module. Therefore, resolve the ModuleNotFoundError issue
    – FisNaN
    Commented Oct 14, 2022 at 11:22
1

Using poetry and pytest 5.4.3, I had the following structure (some folders / files have been removed for clarity):

project structure

.
├── my_app
│   ├── __init__.py
│   ├── main.py
│   ├── model.py
│   └── orm.py
├── poetry.lock
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    ├── conftest.py
    ├── test_my_app.py
    └── utilities
        └── db_postgresql_inmemory.py

tests/conftest.py

pytest_plugins = [
    "utilities.db_postgresql_inmemory",
]

which generated a module not found error for the fixture:

ImportError: Error importing plugin "utilities.db_postgresql_inmemory": No module named 'utilities'

None of the other answers have worked for me, as I have tried to add:

[me@linux ~/code/my_app]touch tests/utilities/__init__.py
[me@linux ~/code/my_app]touch ./test_blank.py

I could make the import from conftest.py work by REMOVING both __init__.py files:

[me@linux ~/code/my_app]rm tests/utilities/__init__.py tests/__init__.py
0

I ran into this issue as well and am using poetry for dependency management and direnv for my project specific environment variables. Please note, I am relatively new to Python so I don't know if this is the correct fix.

Here is my entire .envrc file:

layout_poetry() {
  if [[ ! -f pyproject.toml ]]; then
    log_error 'No pyproject.toml found.  Use `poetry new` or `poetry init` to create one first.'
    exit 2
  fi

  local VENV=$(poetry env list --full-path | cut -d' ' -f1)
  if [[ -z $VENV || ! -d $VENV/bin ]]; then
    log_error 'No created poetry virtual environment found.  Use `poetry install` to create one first.'
    exit 2
  fi
  VENV=$VENV/bin
  export VIRTUAL_ENV=$(echo "$VENV" | rev | cut -d'/' -f2- | rev)
  export POETRY_ACTIVE=1
  PATH_add "$VENV"
}

layout poetry
export PYTHONDONTWRITEBYTECODE=1
export PYTHONPATH="$PWD/project_name"

I don't know if I need to layout poetry because it is supposed to be creating virtual environments for us already but this is what I coworker recommended so I went with it. Layout poetry also didn't work without that function and it didn't like when I added it to my zshenv so I added it here.

For this specific question, the last line is the money maker.

0

I was facing the issue which i resolved by

  • Installing pytest at the root of my project using pip install pytest
  • Adding blank __init__.py in the sibling of my test_file.py which i wanted to execute.
0

I have resolved it by adding export PYTHONPATH="your root dir/src"

i.e.
export PYTHONPATH="/builds/project/src"

poetry run pytest .....

0

The simplest solution I found was to manually add my target module to syspath. Lets say you have a structure like this:

flaskapp
- src
  -- app.py
  -- utils
  -- ...
- tests
docs
venv

This makes my test folder a sibling to my module's src folder. If I start putting test_* files that need to import some of the module's code, I can simply:

import src.utils.calculator

And this would be fine until I try to import a file that imports another file from the module. The solution is simple: add a __init__.py to your tests folder, and put this line inside:

import sys, os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))

And just modify the last part relative to your module location and folder name

0

For me, when I was checking my project structure I found parent directory and sub directory having same names. When I changed the directory name, I got it working. So,

# Did not work
- same_name_project/
    - same_name_project/
    - tests/

# Worked
- different_named_project/
    - a_unique_directory/
    - tests/
0

I noticed that the import in the myproject.py looks like this:

from moduleone import ModuleOne

try replacing the path with the full one, like

from myproject.moduleone import ModuleOne

the initial error looks logical, because there is no moduleone module in the tests directory from which the program is run

it helped in my case

1
  • 1
    Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Aug 20, 2023 at 5:31
0

I didn't see quite this solution yet. I had to add __init__.py to both the root folder and the test folder. Like so:

myproject/
__init__.py
  myproject/
    myproject.py
    moduleone.py
  tests/
    __init__.py
    myproject_test.py

Furthermore the import of the function needs to include the folder name:

from myproject.myproject import MyClass

Not the answer you're looking for? Browse other questions tagged or ask your own question.