4

I have a file dir/subdir/module.py using relative imports so that

python3 dir/subdir/module.py

fails with

ModuleNotFoundError: No module named '__main__.test_forward'; '__main__' is not a package

but

python3 -m dir.subdir.module

runs.

Unsurprisingly,

python3 -m pytest dir/subdir/module.py
pytest dir/subdir/module.py

fail as well. Can I run this module's tests from pytest (without changing it)?

Of course, I've looked at How to test single file under pytest and https://docs.pytest.org/en/latest/usage.html#cmdline, but failed to find an answer.

Here is a minimal reproducing example:

File proj/main.py:

def func():
    return 1

File proj/tests.py:

from .main import func

def test_func():
    assert func() == 1

if __name__ == "__main__":
    test_func()

Note that I omitted __init__.py in proj as per PEP 420.

(venv) romanov@k9-09:~/temp$ python3 -m proj.tests
(venv) romanov@k9-09:~/temp$

Tests run and pass (if I change test_func to fail, I get the expected result).

(venv) romanov@k9-09:~/temp$ python3 proj/tests.py
Traceback (most recent call last):
  File "proj/tests.py", line 1, in <module>
    from .main import func
SystemError: Parent module '' not loaded, cannot perform relative import

With pytest:

(venv) romanov@k9-09:~/temp$ pytest proj/tests.py
============================================================================================ test session starts =============================================================================================
platform linux -- Python 3.5.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/home/romanov/temp/.hypothesis/examples')
rootdir: /home/romanov/temp, inifile:
plugins: hypothesis-4.0.1
collected 0 items / 1 errors

=================================================================================================== ERRORS ===================================================================================================
_______________________________________________________________________________________ ERROR collecting proj/tests.py _______________________________________________________________________________________
proj/tests.py:1: in <module>
    from .main import func
E   SystemError: Parent module '' not loaded, cannot perform relative import
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================================================================== 1 error in 0.09 seconds ===========================================================================================

Unfortunately, this doesn't fully reproduce the problem; here pytest --pyargs proj.tests suggested in ivan_pozdeev's answer says ERROR: file or package not found: proj.tests (missing __init__.py?) and adding empty proj/__init__.py lets it run.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 1
    I guess you're trying to run as a test the main file and not a test file. Could you edit the question, adding all the import of the test file? – pittix Mar 26 '19 at 07:26
  • @pittix The file contains tests, its `if __name__ == '__main__'` section just calls the test functions. I was trying to run it under pytest to get better error messages. The file is also part of a large project. I've tried to make a smaller reproducing example, but didn't succeed completely (see the edit). – Alexey Romanov Mar 26 '19 at 08:48
  • Your `test_func` is not a Python `unittest` test so pytest won't find it AFAIK. – ivan_pozdeev Mar 26 '19 at 14:05
  • 2
    @ivan_pozdeev It will, because it starts with `test_`: https://docs.pytest.org/en/latest/goodpractices.html#test-discovery (or just look at the starting example in https://docs.pytest.org/en/latest/). – Alexey Romanov Mar 26 '19 at 14:10
  • @ivan_pozdeev And it does find and run `test_func` if `proj/__init__.py` is added, as mentioned at the end. – Alexey Romanov Mar 26 '19 at 14:15
  • Possible duplicate of [How do I Pytest a project using PEP 420 namespace packages?](https://stackoverflow.com/questions/50174130/how-do-i-pytest-a-project-using-pep-420-namespace-packages) – ivan_pozdeev Mar 27 '19 at 10:17

2 Answers2

2

According to Usage and Invocations — pytest documentation:

Run tests from packages

pytest --pyargs pkg.testing

This will import pkg.testing and use its filesystem location to find and run tests from.

In [9]: !ls -R t
t:
__init__.py  main.py  tests.py

In [10]: pwd
Out[10]: u'c:\\Users\\Sasha\\Desktop'

In [7]: os.environ['PYTHONPATH']=r'c:\Users\Sasha\Desktop'

In [8]: !pytest --pyargs t.tests
============================= test session starts =============================
platform win32 -- Python 2.7.15+, pytest-3.0.3, py-1.5.4, pluggy-0.4.0
rootdir: c:\Users\Sasha\Desktop, inifile:
plugins: xdist-1.15.0, mock-1.5.0, instafail-0.3.0
collected 1 items

t\tests.py .

========================== 1 passed in 0.02 seconds ===========================

If you omit __init__.py as per PEP 420, pytest becomes confused because the core premise of its logic is to search the subtree for test files and import them. Without __init__.py, it has no way to find out if it should import files as subpackages or as standalone modules.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • Didn't see it, but I get `ImportError: attempted relative import with no known parent package` when trying. – Alexey Romanov Mar 26 '19 at 08:05
  • @AlexeyRomanov WFM for the scenario you described, see update. – ivan_pozdeev Mar 26 '19 at 15:07
  • In the scenario I described, `__init__.py` is in the parent directory (or absent), not in the same directory where `main.py` and `tests.py` are. (As far as I understand, this is only allowed since Python 3.3: https://docs.python.org/3/whatsnew/3.3.html#pep-420-implicit-namespace-packages) – Alexey Romanov Mar 27 '19 at 07:27
  • @AlexeyRomanov you said: _"empty `__init__.py`"_, not "omitted `__init__.py` as per PEP 420". – ivan_pozdeev Mar 27 '19 at 09:02
  • Yes, empty `__init__.py` _in the parent directory_: `__init__.py`, `proj/main.py`, and `proj/tests.py`. – Alexey Romanov Mar 27 '19 at 09:24
  • @AlexeyRomanov there's absolutely no reason to place `__init__.py` in the parent directory because you are not using the parent directory as a package in your example -- that file is never used. So I assumed that you omitted the path for brevity. Because inaccurate descriptions are common on this site, I assume probable rather than extremily improbable things. Extremily strange and improbable details of your setup is something that you specifically mention and emphasize. – ivan_pozdeev Mar 27 '19 at 10:23
  • Ok, I deleted the mention of `__init__.py`. – Alexey Romanov Mar 27 '19 at 10:29
1

The answer is in https://docs.pytest.org/en/latest/goodpractices.html#tests-as-part-of-application-code:

You can use Python3 namespace packages (PEP420) for your application but pytest will still perform test package name discovery based on the presence of __init__.py files. If you use one of the two recommended file system layouts above but leave away the __init__.py files from your directories it should just work on Python3.3 and above. From “inlined tests”, however, you will need to use absolute imports for getting at your application code.

So my desired combination isn't possible without adding __init__.py files.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487