Friday, 21 January 2022

Import problem when separating applications and package tests

Maybe my goal and what I try to do here is wrong in the meaning of unpythonic. I am open for any suggestions about that.

My goals

  1. Application (myapp) with its own tests folder.
  2. A package (mypackage) with its own tests folder.
  3. The tests for the package should be runable from the application folder and from the package folder.
  4. The package have implicit and explicit components. The latter need to be imported explicit (e.g. via import mypackage.mymoduleB).
  5. The package (folder) can be copied (shipped for reuse in other applications?) to other file system locations without loosing its functionality and testabilibty. That is why tests is inside the package folder and not outside.

That is the folder tree where itest is the name of the project, myapp is the application with an if __name__ == '__main__': in it and mypackag is the package.

itest
└── myapp
    ├── myapp.py
    ├── mypackage
    │   ├── __init__.py
    │   ├── _mymoduleA.py
    │   ├── mymoduleB.py
    │   └── tests
    │       ├── __init__.py
    │       └── test_all.py
    └── tests
        ├── __init__.py
        └── test_myapp.py

The problem

I can run the unittests from the application directory without problems.

/home/user/tab-cloud/_transfer/itest/myapp $ python3 -m unittest -vvv
test_A (mypackage.tests.test_all.TestAll) ... mymoduleA.foo()
ok
test_B (mypackage.tests.test_all.TestAll) ... mymoduleB.bar()
ok
test_myname (tests.test_myapp.TestMyApp) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

But when I go inside the package the tests do not run (sieh goal #3).

/home/user/tab-cloud/_transfer/itest/myapp/mypackage $ python3 -m unittest -vvv
tests.test_all (unittest.loader._FailedTest) ... ERROR

======================================================================
ERROR: tests.test_all (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests.test_all
Traceback (most recent call last):
  File "/usr/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "/usr/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "/home/user/tab-cloud/_transfer/itest/myapp/mypackage/tests/test_all.py", line 12, in <module>
    from . import mypackage
ImportError: cannot import name 'mypackage' from 'tests' (/home/user/tab-cloud/_transfer/itest/myapp/mypackage/tests/__init__.py)


----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

The MWE

No I show you the files. To make sure the tests for the package using the right import when run from the application folder or from the package folder I use importlib (based on foreign solution).

The three files form the package

This is myapp/mypackage/__init__.py:

# imported implicite via 'mypackage'
from ._mymoduleA import *

# 'mymoduleB' need to be imported explicite
# via 'mypackage.moduleB'

This is myapp/mypackage/_mymoduleA.py:

def foo():
    print('mymoduleA.foo()')
    return 1

This is myapp/mypackage/mymoduleB.py:

def bar():
    print('mymoduleB.bar()')
    return 2

The tests for the package

The myapp/mypackage/tests/__init__.py is empty.

This is myapp/mypackage/tests/test_all.py:

import importlib
import unittest

# The package should be able to be tested by itself (run unittest inside the
# package directory) AND from the using application (run unittest in
# application directory).
# Based on: https://stackoverflow.com/a/14050282/4865723
if importlib.util.find_spec('mypackage'):
    import mypackage
    import mypackage.mymoduleB
else:
    from . import mypackage
    from mypackage import mymoduleB


class TestAll(unittest.TestCase):
    def test_A(self):
        self.assertEqual(1, mypackage.foo())

    def test_B(self):
        self.assertEqual(2, mypackage.mymoduleB.bar())

The application

This is cat myapp/myapp.py:

#!/usr/bin/env python3

import mypackage


def myname():
    return 'My application!'


if __name__ == '__main__':
    print(myname())

    mypackage.foo()

    try:
        mypackage.mymoduleB.bar()
    except AttributeError:
        # we expecting this
        print('Not imported yet: "mymoduleB.bar()"')

    # this should work
    import mypackage.mymoduleB
    mypackage.mymoduleB.bar()

The test for the application

The myapp/tests/__init__.py is empty.

This is myapp/tests/test_myapp.py:

import unittest
import myapp


class TestMyApp(unittest.TestCase):
    def test_myname(self):
        self.assertEqual(myapp.myname(), 'My application!')

Sidenotes

Please let me explain something more about my goals. The mypackage should be reusable in other projects. In practice this means I copy the mypackage folder from one place to another. And while copy that folder I do want that tests folder come with it without explicte thinking about it because it is outside the package folder. And if the new project does unittesting the tests of the package should be involved in that unittesting automaticlly (via discover).



from Import problem when separating applications and package tests

No comments:

Post a Comment