0

I'm struggling to get imports to work for my python project. I've created a test project to illustrate my issue. This is with Python 3.

My directory structure is as follows:

project/
  test.sh
  packageA/
     __init__.py
     moduleA.py
     test/
        __init__.py
        test_moduleA.py

The contents of test.sh is python3 packageA/test/test_moduleA.py

Both __init__py are empty.

This is moduleA.py:

class A:
  def doSomething(self):
    print("Did something")

This is 'test_moduleA.py`:

import unittest
from packageA.moduleA import A

class TestModuleA(unittest.TestCase):
  def testSomething(self):
    a = A()
    self.assertTrue(a.doSomething() == "Did Something")

if __name__ == '__main__':
    unittest.main()

When I run test.sh this is the error I get:

[project] $ ./test.sh 
Traceback (most recent call last):
  File "packageA/test/test_moduleA.py", line 2, in <module>
    from packageA.moduleA import A
ModuleNotFoundError: No module named 'packageA'
[project] $ 

I have tried using the relative import in `test_moduleA.py' as follows:

from ..moduleA import A

In this case there error I get is shown below:

[project] $ ./test.sh 
Traceback (most recent call last):
  File "packageA/test/test_moduleA.py", line 2, in <module>
    from ..moduleA import A
ValueError: attempted relative import beyond top-level package
[project] $

How do I get this to work properly? What is the Pythonic way to do it?

DungeonTiger
  • 627
  • 1
  • 9
  • 21

2 Answers2

1

This is my suggested solution, based on this answer (see also here) and this article.

First of all, move the test directory into the root of your project:

project/
  test.sh
  packageA/
     __init__.py
     moduleA.py
  test/
     __init__.py
     test_moduleA.py

Then, change the test_moduleA.py as follows (note the import):

import unittest
from packageA.moduleA import A


class TestModuleA(unittest.TestCase):
    def testSomething(self):
        a = A()
        self.assertTrue(a.do_something() == "Something was done")

The test.sh is this one:

#!/bin/bash
TEST_PACKAGE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

cd ${TEST_PACKAGE}
python3 -m unittest test.test_moduleA

I've changed also the a class in order to pass the test:

class A:
    def do_something(self):
        print("do_somwthing was called")
        return "Something was done"

Now, run test.sh and it should work as expected:

$ ./test.sh 
do_somwthing was called
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
PieCot
  • 3,564
  • 1
  • 12
  • 20
  • Thank you. This is an interesting solution. I'll give it a try. However, I think I would like to keep my test directory as a child of the package. I'm going to likely have a number of sibling packages so keeping the tests under them helps to keep things organized. How would this solution be modified for that? – DungeonTiger Jun 18 '18 at 01:33
  • This does the trick `python3 -m unittest packageA.test.test_moduleA` – DungeonTiger Jun 18 '18 at 11:26
0

Using shell scripts to test/deploy python stuff isn't really a good idea, so I propose you instead replacing test.sh with test.py, whose content would be:

import unittest

testmodules = [
    'packageA.test.test_moduleA',
]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

Adding new tests to it would be quite easy, you'd just need to modify the testmodules list.

BPL
  • 9,632
  • 9
  • 59
  • 117
  • I intend to use something like gradle since this is part of a bigger project. The shell script is just to help illustrate the problem. I will try this solution and see how it goes. Still it does not explain why my imports are not working. – DungeonTiger Jun 18 '18 at 01:31