Somewhat convenient folder structure for Python unit tests.
Simple package
Let’s say we need a simple Python package which implements some functionality
and supposed to be a part of something bigger.
It must be also very simple to use from other packages,
just like import my_package
.
When we start developing our Python package it usually looks like the following:
my_package
├── __init__.py
├── one_class.py
└── another_class.py
Adding unit tests
After some time, the sooner the better, we add unit tests, which tend to hide among functional files. So we mix implementation and test files, like this:
my_package
├── __init__.py
├── one_class.py
├── another_class.py
├── test_one_class.py
└── test_another_class.py
We typically write tests using unittest library.
In tests we do import one_class
etc to load implementation modules.
So test_one_class.py
may look like:
"""
Typical test cases for one_class module
"""
import unittest
import one_class
class TestOneClass(unittest.TestCase):
"""Unittest class for one_class module"""
def test_one_class(self):
"""Test one_class"""
self.assertTrue(one_class.One.something)
if __name__ == "__main__":
unittest.main()
From my_package
unit tests can be executed using the following command:
python -m unittest discover
Moving to test
folder
Someone likes this mix of implementation and tests, but not me.
I prefer test files to be separated from module implementation.
At the same time, test files should belong to the module and should be located
in a special folder called test
, like this:
my_package
├── __init__.py
├── one_class.py
├── another_class.py
└── test
├── test_one_class.py
└── test_another_class.py
Tests still can be ran from my_package
folder,
however we should define start directory using --start-directory
or just -s
:
python -m unittest discover -s test
How to run tests from test
folder?
The only problem may happen if you would like to run tests from test
directory.
Unfortunately in Python we cannot import modules from upper level.
But this can be solved with the following function:
import imp
import os
def load_upper_module(file_name):
"""Load module from upper directory (..) by file name"""
module_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, file_name))
return imp.load_source(file_name, module_path)
This function takes Python module file name, e.g. one_class.py
in our case.
It can be called from setUpClass
class method of unittest.TestCase
to load module to cls.module_under_test
every time when TestCase is loaded.
The following helper class shows how to do that:
class TestCase(unittest.TestCase):
"""TestCase class which loads module_file_name as a module under test"""
@classmethod
def setUpClass(cls):
"""Load module"""
cls.module_under_test = load_upper_module(cls.module_file_name)
We can put both load_upper_module
and helper TestCase
class
into, e.g. utils
module.
Then use it like this:
"""
Test cases for one_class module
Module utils implements TestCase helper class
which loads module from upper directory
"""
import unittest
import utils
class TestOneClass(utils.TestCase):
"""Unittest class for one_class module"""
module_file_name = "one_class.py"
def test_one_class(self):
"""Test one_class"""
self.assertTrue(self.module_under_test.One.something)
if __name__ == "__main__":
unittest.main()
Now we can run tests from test
and from my_package
folders.
Find sample code here.