0

I have the following folder structure in my project:

my-project
   src/
       __init__.py
       script.py
   test/
       __init__.py
       test_script.py

Ideally I want to have a separate folder where all the unit tests go. My test_script.py looks something like this:

from src.script import my_object

class TestClass(unittest.TestCase):
    
    def test_script_object(self):
        # unit test here
        pass

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

When I try to run the script (using python test_script.py) I get the following error:

Traceback (most recent call last):
  File "test_script.py", line 4, in <module>
    from src.script import my_object
ModuleNotFoundError: No module named 'src'

I was following the instructions from this other thread, and I even tried appending src to the sys path (which forces me to change how I do imports in the rest of the project). When I'm not trying to append to the sys path, both of my __init__.py files are empty.

I am using python 3.8.

Does anyone have any suggestions? I'm new to unit testing in python, so maybe there is a better structure or other conventions I'm not aware of. Thanks in advance for your help!

ellen
  • 571
  • 7
  • 23
  • "_Does anyone have any suggestions?_" This site is for _complete_ answers to _precise_, on-topic questions. Can you please ask a precise question? You can see [ask] for guidance. – starball Jan 06 '23 at 23:01
  • It worked for me when I fixed the pythonpath. And I also fixed the extra ".py" in the import line, but that can't be there with the error messages you got, so I don't think you're showing us the code you ran. So ... you said "sys path" which I hope doesn't mean system path. – Kenny Ostrom Jan 06 '23 at 23:20
  • @starball thanks for more specific Meta reference questions; I added them to my saves. – Karl Knechtel Jan 07 '23 at 00:17
  • 1
    That directory layout looks a little suspect...in particular, having `src/__init__.py` looks odd, because do you really want `src` to be an importable module? – larsks Jan 07 '23 at 01:27
  • also, `from src.script.py import my_object` is an invalid import statement; you don't include the `.py` extension on `import`s. – larsks Jan 07 '23 at 01:33

1 Answers1

1

Generally, any instructions that have you modifying sys.path in order to run your tests are sending you in the wrong direction. Your testing tool should be able to discover both your tests and your application code without requiring that sort of hackery.

I generally use pytest for running my tests. I would structure your example like this:

my-project/
├── src
│   ├── conftest.py
│   └── myproject
│       ├── __init__.py
│       └── script.py
└── tests
    ├── __init__.py
    └── test_script.py

Assuming that src/myproject/script.py looks like this:

def double(x: int):
    return x*2

And tests/test_script.py look like this:

import myproject.script

def test_double():
    res = myproject.script.double(2)
    assert res == 4

I can run tests from the my-project directory by simply running pytest:

$ pytest
========================================== test session starts ==========================================
platform linux -- Python 3.11.1, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/lars/tmp/python/my-project
collected 1 item

tests/test_script.py .                                                                            [100%]

=========================================== 1 passed in 0.00s ===========================================

In this setup, the file src/conftest.py is what allows pytest to automatically discover the location of your application code. Alternatively, you could instead specify that in your pyproject.toml file like this:

[tool.pytest.ini_options]
pythonpath = [
  "src"
]

pytest also works if you write unittest-style tests as you show in your question; the above behavior would be identical if tests/test_script.py looked like:

import unittest
import myproject.script

class TestScript(unittest.TestCase):
    def test_double(self):
        res = myproject.script.double(2)
        self.assertEqual(res, 4)

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

(But I prefer pytest's simpler syntax.)

Possibly useful reading:


If you really want to use unittest you can use the same directory layout, but you will need to update sys.path for it to run correctly. The easiest way to do that is:

PYTHONPATH=$PWD/src python -m unittest

This will automatically discover all your tests and run them. You can run a single test like this:

PYTHONPATH=$PWD/src python -m tests.test_script

You can avoid needing to modify sys.path if you use a simpler directory layout:

my-project/
├── myproject
│   ├── __init__.py
│   └── script.py
└── tests
    ├── __init__.py
    └── test_script.py

Both pytest and python -m unittest, when run from the my-project directory, will correctly discover and run your tests without requiring any path modifications.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • Thank you! I was trying to run from the /test/ directory (rookie mistake) and now I am able to run my tests with just `python -m unittest`. I read through the article you linked and will definitely consider switching to pytest. Thanks for your help! – ellen Jan 08 '23 at 20:10