My project looks like this:
|- src/
| |- my_package/
| |- __init__.py
| |- ...
|
|- tests/
| |- group1/
| | |- __init__.py (empty)
| | |- test_thing_1.py
| |- ...
| |- base_test_case.py
|
|- pyproject.toml
Here test_thing_1.py
(and other test files) rely on a base TestCase class inside base_test_case.py
.
How should I import this base class into a test file?
Some more detail. With the structure above, you would run tests by installing the source first and then running unittest
:
pip install -e .
python -m unittest discover tests/
Each test file (e.g. test_thing_1.py
) can access the package source as a proper global import (the src/
directory protects from ambiguous packages):
from mypackage import MyClass
class SomeTestCase(TestBase):
# ...
But now I also have a base test case, that I will extend for each real test case:
# file: base_test_case.py
class TestBase(unittest.TestCase):
# ...
And I want to organize my tests in subfolders. That's fine, as long as each folder contains an __init__.py
file.
And so now: how can I import TestBase
in my test files such that the tests run robustly with:
python tests/group1/test_thing_1.py
python -m unittest discover tests/group1/
What I tried:
from mypackage import MyClass
# OR:
from ..base_test_case import TestBase
# OR:
from .base_test_case import TestBase
# OR:
from base_test_case import TestBase
class SomeTestCase(TestBase):
# ...
python -m unittest ...
python -m unittest ...
, otherwise failspython -m unittest ...
but fails otherwiseI figure I could install the base test case with the rest of my package, to make an absolute import possible. However, I don't want to ship anything related to my tests.
I have recreated your directory structure:
$ tree . -I __pycache__ -I my_package.egg-info
.
├── pyproject.toml
├── src
│ └── my_package
│ └── __init__.py
└── tests
├── base_test_case.py
└── group1
├── __init__.py
└── test_thing1.py
4 directories, 5 files
my_package.__init__.py
has the following contents:
class MyClass:
def times2(self, value):
return value * 2
base_test_case.py
has:
import unittest
class TestBase(unittest.TestCase):
expected = 22
test_value = 11
test_thing1.py
has:
from my_package import MyClass
from tests.base_test_case import TestBase
class SomeTestCase(TestBase):
def test_times2(self):
my_class = MyClass()
self.assertEqual(my_class.times2(TestBase.test_value), TestBase.expected)
And pyproject.toml
has:
[project]
name = "my_package"
version = "1.0.0"
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["src"]
include = ["my_package*"]
namespaces = false
On the command line I do:
$ pip install -e .
Obtaining file://$HOME/PycharmProjects/78297862
Installing build dependencies ... done
Checking if build backend supports build_editable ... done
Getting requirements to build editable ... done
Installing backend dependencies ... done
Preparing editable metadata (pyproject.toml) ... done
Building wheels for collected packages: my_package
Building editable for my_package (pyproject.toml) ... done
Created wheel for my_package: filename=my_package-1.0.0-0.editable-py3-none-any.whl size=1203 sha256=b962da340d5bcc9fe909c2e58bf60a028a8475a4fe1f3a122cefb8d16f92c92c
Stored in directory: /tmp/pip-ephem-wheel-cache-6ekafjm5/wheels/a8/9c/cc/682ba2654943278b924e62dcc738360a832c1757bfcf5393a9
Successfully built my_package
Installing collected packages: my_package
Successfully installed my_package-1.0.0
If I run the two variants of command line that you have in your question I get:
(.venv) 78297862$ python -m unittest discover tests/
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
(.venv) 78297862$ python -m unittest discover tests/group1/
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
It is also working inside of PyCharm.
The main difference seems to be that I am doing an absolute import of TestBase
inside to test_thing.py
with:
from tests.base_test_case import TestBase