vastlysuperiorman vastlysuperiorman - 1 month ago 16
Python Question

More elegant solution for python imports across app/tests?

I'm trying to keep my code reasonably organized by putting tests in a separate directory from my app. However, imports work either for the app or for the tests, but not both. Here's a contrived example that illustrates my current problem:

myapp/
app/
main.py
settings.py
moods.py
test/
test_moods.py


Contents of files as follows:

main.py

import settings
from moods import Moods

words = Moods(settings.EXAMPLE)
print(words.excited())


settings.py

EXAMPLE = "Wow$ Python$"
DELIM = "$"


moods.py

import settings

class Moods:
def __init__(self, text):
self.text = text

def excited(self):
return self.text.replace(settings.DELIM, "!!!")


test_moods.py

import sys, os, unittest
sys.path.insert(0, os.path.abspath('..'))

from app.moods import Moods

class TestMood(unittest.TestCase):
def setUp(self):
self.words = Moods("Broken imports$ So sad$")

def test_mood(self):
self.assertEqual(self.words.excited(), "Broken imports!!! So sad!!!")
with self.assertRaises(AttributeError):
self.words.angry()

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


In the current state, from
myapp/
I can run the following successfully:

>> python3 app/main.py
Wow!!! Python!!!


But when I try to run tests, the import fails in moods.py:

>> python3 -m unittest discover test/
ImportError: Failed to import test module: test_moods
[ stack trace here pointing to first line of moods.py ]
ImportError: No module named 'settings'


If I modify line 1 of moods.py to read
from app import settings
, the test will pass, but normal execution of the app fails because it sees no module
app
.

The only solutions I can think of are


  1. Putting the tests in the same directory as the code (which I was trying to avoid)

  2. Adding the
    sys.path.insert(0, os.path.abspath('..'))
    to each file in my app to make the imports work the same as the test ones do (which seems messy)



Is there are more elegant way to solve this import problem?

Answer

You should have both app and test directory in PYTHONPATH.

One way is to replace this:

sys.path.insert(0, os.path.abspath('..'))

from app.moods import Moods

with this:

sys.path.insert(0, os.path.abspath('../app'))

from moods import Moods

Or you can set the PYTHONPATH environament variable before running the tests.

Why? Because when you run main.py, then app is in the path, not app/.., so you want ot have the same when running tests.