Search code examples
pythonpython-importpython-unittest

Python Unittest - How to properly handle tests in different folders and shared base testcases?


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/
  • From PyCharm

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):
    # ...
  • The first one works for triggering a test inside PyCharm, but fails for running python -m unittest ...
  • The second one works for running python -m unittest ..., otherwise fails
  • The third one works for python -m unittest ... but fails otherwise

I 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.


Solution

  • 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. enter image description here

    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