6

There were already questions regarding this topic. Sometimes programmers put some __init__.py at some places, often it is said one should use absolute paths. However, I don't get it to work here:

How do I import a class from a package so that tests in pytest run and the code can be used?

At the moment I get pytest or the code passing respective running.

My example project structure is

.
├── testingonly
│   ├── cli.py
│   ├── __init__.py
│   └── testingonly.py
└── tests
    ├── __init__.py
    └── test_testingonly.py

__init__.py is in both cases an empty file.

$ cat testingonly/cli.py
"""Console script for testingonly."""
from testingonly import Tester

def main(args=None):
    """Console script for testingonly."""
    te = Tester()
    return 0

main()
$ cat testingonly/testingonly.py
"""Main module."""
class Tester():
    def __init__(self):
        print("Hello")

This gives - as expected:

$ python3 testingonly/cli.py
Hello

Trying to test this, however, fails:

$ pytest
========================================================= test session starts =========================================================
platform linux -- Python 3.7.3, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /home/stefan/Development/testingonly
collected 0 items / 1 error                                                                                                           

=============================================================== ERRORS ================================================================
_____________________________________________ ERROR collecting tests/test_testingonly.py ______________________________________________
ImportError while importing test module '/home/stefan/Development/testingonly/tests/test_testingonly.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.7/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
tests/test_testingonly.py:10: in <module>
    from testingonly import cli
testingonly/cli.py:2: in <module>
    from testingonly import Tester
E   ImportError: cannot import name 'Tester' from 'testingonly' (/home/stefan/Development/testingonly/testingonly/__init__.py)

Renaming testingonly/testingonly.py to testingonly/mytest.py and changing the imports in test_testingonly.py (from testingonly import mytest) and cli.py (from mytest import Tester) gives

$ pytest
========================================================= test session starts =========================================================
platform linux -- Python 3.7.3, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /home/stefan/Development/testingonly
collected 0 items / 1 error                                                                                                           

=============================================================== ERRORS ================================================================
_____________________________________________ ERROR collecting tests/test_testingonly.py ______________________________________________
ImportError while importing test module '/home/stefan/Development/testingonly/tests/test_testingonly.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.7/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
tests/test_testingonly.py:10: in <module>
    from testingonly import cli
testingonly/cli.py:2: in <module>
    from mytest import Tester
E   ModuleNotFoundError: No module named 'mytest'
======================================================= short test summary info =======================================================
ERROR tests/test_testingonly.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================================== 1 error in 0.37s ===========================================================
$ python3 testingonly/cli.py
Hello

The other proposed solution with renaming to mytest.py lets the tests pass, but in cli.py using from testingonly.mytest import Tester gives a NameNotFound error.

$ python3 testingonly/cli.py 
Traceback (most recent call last):
  File "testingonly/cli.py", line 2, in <module>
    from testingonly.mytest import Tester
ModuleNotFoundError: No module named 'testingonly'
$ pytest
========================================================= test session starts =========================================================
platform linux -- Python 3.7.3, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /home/stefan/Development/testingonly
collected 1 item                                                                                                                      

tests/test_testingonly.py .                                                                                                     [100%]

========================================================== 1 passed in 0.12s ==========================================================
Stefan Bollmann
  • 640
  • 4
  • 12
  • 32
  • This answer on relative Python Imports may help as well https://stackoverflow.com/questions/2183205/importing-a-module-in-nested-packages – DogEatDog Jan 03 '22 at 19:03
  • First of all, `from testingonly import Tester` is wrong. The `Tester` class is defined in module with qualname `testingonly.testingonly` as is not exported in `testingonly/__init__.py`, so the import will not resolve. It works in `testingonly.cli` only because it is on the same level as `testingonly.testingonly`, with a relative import as fallback. To verify, create some `foo.py` in the root dir with a single line `from testingonly import Tester` and run `python foo.py` - you will get the same error. – hoefling Jan 04 '22 at 16:41
  • Second, `pytest` doesn't add the project root dir to `sys.path` by default - you should take care of it yourself. Either run `python -m pytest`, or add an empty file named `conftest.py` to the project root dir. Check out the answers in [PATH issue with pytest 'ImportError: No module named YadaYadaYada'](https://stackoverflow.com/q/10253826/2650249) for more details. – hoefling Jan 04 '22 at 16:43
  • Ah, and the correct replacement for `from testingonly import Tester` is of course `from testingonly.testingonly import Tester` – hoefling Jan 04 '22 at 16:44

2 Answers2

2

The self-named module testingonly and file name of testingonly.py may be causing some issues with the way the modules are imported.

Remove the __init__.py from the tests directory. Ref this answer .

Try renaming testingonly.py to mytest.py and then importing it into your project again.

In the cli.py, it should be:

from testingonly.mytest import Tester

And then for your test file test_testingonly.py:

from testingonly.mytest import Tester

You're test_testingonly.py file should look like this:

import pytest
from testingonly.mytest import Tester  # Import the Tester Class


def test_tester(capsys):
    # Create the Tester Class
    te = Tester()
    # Get the captured output
    captured = capsys.readouterr()
    # Assert that the capture output is tested
    assert captured.out == "Hello\n"

Finally, Run your tests with:

python -m pytest tests/

Here is a fully working example based off of your code: https://github.com/cdesch/testingonly

DogEatDog
  • 2,899
  • 2
  • 36
  • 65
2

This is only a python path issue. Your pytest is unable to find the path to source modules.

One way to fix this: export PYTHONPATH=$PYTHONPATH:. before running pytest. Here you are saying that your current folder represented by a dot is a python module path. This is what happens when you also run via python -m pytest, where the module path gets set automatically.

But this requires you to set the PYTHONPATH every time before you run pytest, or use a long command for pytest which is very inconvenient.

One earlier method to overcome this issue: install pytest-pythonpath plugin to automatically add PYTHONPATH to environment. This module is obsolete now (as seen in its home page) because pytest.ini allows you to specify python path directly from v7 onwards

So now all you have to do is this: ensure that your pytest is version 7 or above, and your pytest.ini file should contain these lines:

[pytest]
testpaths = tests
python_files = tests.py test_*.py
pythonpath = .

Now your pytest will have no trouble finding your source modules. You may also add your source folders to pythonpath in pytest.ini if required

pytest documentation for pythonpath: https://docs.pytest.org/en/7.0.x/reference/reference.html#confval-pythonpath

Mani
  • 23,635
  • 6
  • 67
  • 54
  • Update your `PYTHONPATH` in the `venv` `activate` script. I presume this is what IDEs always do anyway. It seems IDEs can do whatever crap they like and no-one calls it a hack, and then they say something like "ooh have you tried PyCharm it's so cool". Then they start complaining about "works for me" inconsistency problems. – NeilG Jul 26 '23 at 07:03