The best solution in my opinion is to use the unittest command line interface which will add the directory to the sys.path so you don't have to (done in the TestLoader class).

For example for a directory structure like this:

new_project
β”œβ”€β”€ antigravity.py
└── test_antigravity.py

You can just run:

$ cd new_project
$ python -m unittest test_antigravity

For a directory structure like yours:

new_project
β”œβ”€β”€ antigravity
β”‚   β”œβ”€β”€ __init__.py         # make it a package
β”‚   └── antigravity.py
└── test
    β”œβ”€β”€ __init__.py         # also make test a package
    └── test_antigravity.py

And in the test modules inside the test package, you can import the antigravity package and its modules as usual:

# import the package
import antigravity

# import the antigravity module
from antigravity import antigravity

# or an object inside the antigravity module
from antigravity.antigravity import my_object

Running a single test module:

To run a single test module, in this case test_antigravity.py:

$ cd new_project
$ python -m unittest test.test_antigravity

Just reference the test module the same way you import it.

Running a single test case or test method:

Also you can run a single TestCase or a single test method:

$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method

Running all tests:

You can also use test discovery which will discover and run all the tests for you, they must be modules or packages named test*.py (can be changed with the -p, --pattern flag):

$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

This will run all the test*.py modules inside the test package.

Here you can find the updated official documentation of discovery.

Answer from Pierre on Stack Overflow
Top answer
1 of 16
949

The best solution in my opinion is to use the unittest command line interface which will add the directory to the sys.path so you don't have to (done in the TestLoader class).

For example for a directory structure like this:

new_project
β”œβ”€β”€ antigravity.py
└── test_antigravity.py

You can just run:

$ cd new_project
$ python -m unittest test_antigravity

For a directory structure like yours:

new_project
β”œβ”€β”€ antigravity
β”‚   β”œβ”€β”€ __init__.py         # make it a package
β”‚   └── antigravity.py
└── test
    β”œβ”€β”€ __init__.py         # also make test a package
    └── test_antigravity.py

And in the test modules inside the test package, you can import the antigravity package and its modules as usual:

# import the package
import antigravity

# import the antigravity module
from antigravity import antigravity

# or an object inside the antigravity module
from antigravity.antigravity import my_object

Running a single test module:

To run a single test module, in this case test_antigravity.py:

$ cd new_project
$ python -m unittest test.test_antigravity

Just reference the test module the same way you import it.

Running a single test case or test method:

Also you can run a single TestCase or a single test method:

$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method

Running all tests:

You can also use test discovery which will discover and run all the tests for you, they must be modules or packages named test*.py (can be changed with the -p, --pattern flag):

$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

This will run all the test*.py modules inside the test package.

Here you can find the updated official documentation of discovery.

2 of 16
76

I've had the same problem for a long time. What I recently chose is the following directory structure:

project_path
β”œβ”€β”€ Makefile
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ script_1.py
β”‚   β”œβ”€β”€ script_2.py
β”‚   └── script_3.py
└── tests
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ test_script_1.py
    β”œβ”€β”€ test_script_2.py
    └── test_script_3.py

and in the __init__.py script of the test folder, I write the following:

import os
import sys
PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
    PROJECT_PATH,"src"
)
sys.path.append(SOURCE_PATH)

Super important for sharing the project is the Makefile, because it enforces running the scripts properly. Here is the command that I put in the Makefile:

run_tests:
    python -m unittest discover .

The Makefile is important not just because of the command it runs but also because of where it runs it from. If you would cd in tests and do python -m unittest discover ., it wouldn't work because the init script in unit_tests calls os.getcwd(), which would then point to the incorrect absolute path (that would be appended to sys.path and you would be missing your source folder). The scripts would run since discover finds all the tests, but they wouldn't run properly. So the Makefile is there to avoid having to remember this issue.

I really like this approach because I don't have to touch my src folder, my unit tests or my environment variables and everything runs smoothly.

🌐
GitHub
gist.github.com β€Ί tasdikrahman β€Ί 2bdb3fb31136a3768fac
Typical Directory structure for python tests Β· GitHub
$ python -m unittest tests ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK ... You need to add __init__.py to the tests directory, If you have other subdirectories in the tests directory you also need to add the __init__.py so python can know that those are modules, not just directories.
Discussions

python - unittest directory structure - cannot import src code - Stack Overflow
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 More on stackoverflow.com
🌐 stackoverflow.com
unit testing - Separate src and test directories with Python - Stack Overflow
I am getting a compilation error on the from line in the test file when I place my test and src files in different subfolders: "Unable to import 'classA' pylint(import-error). It was working when t... More on stackoverflow.com
🌐 stackoverflow.com
How to import project source code from the test folder with unittest ?
No, you don't need to modify sys.path. Use from src.module import func in your tests, that should be fine. Start your tests from the root folder of your project. Do not forget to include __init__.py. From unittest docs (BTW these should be your main source of truth for a while): Start directory and subdirectories containing tests must be regular package that have __init__.py file. Side note: consider using pytest :-) More on reddit.com
🌐 r/learnpython
3
2
November 13, 2023
How to generate unit tests for Python using src and test folder structure (preferable with PyCharm integration)? - Stack Overflow
PyCharm does not automatically ... source directory src/foo instead. That would result in a test file located in the same folder as the tested file. The key combination does not work for files/modules that do not contain a class or function but just provide a property. PyCharm generates unwanted init.py files. Therefore the built in unit test generation of PyCharm does not work for me. => What is the recommended way to generate unit tests for Python? I also tried to use UnitTestBot or pynguin ... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Codesolid
codesolid.com β€Ί how-to-separate-tests-and-source-for-python-tests
How To Separate Source and Tests in Python β€” CodeSolid.com 0.1 documentation
The best solution in my opinion is one that looks really strange for a Java developer (or even for someone familiar with Node.js), but it’s fairly idiomatic in Python. That’s simply to realize that src directory doesn’t need to be there!
Top answer
1 of 1
5

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 like to 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:

  • Good integration practices

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.

🌐
Medium
medium.com β€Ί @jamessilman β€Ί python-unittest-from-an-external-directory-3f7a0f7b636d
Python unittest from an external directory | by James Silman | Medium
March 11, 2019 - If you’ve ever wanted to better ... in Python, such as when creating unit tests, below is a simple example of how you achieve this. The first screenshot below for my_class.py shows a simple class called MyClass (no brains sprained here) with one method that just returns true for this example. This class file is located in/src/sub_folder. The second screenshot below for test_my_class.py shows the meat of this simple example. Line 2. Import the unittestlibrary...
🌐
Stack Overflow
stackoverflow.com β€Ί questions β€Ί 61706293 β€Ί separate-src-and-test-directories-with-python
unit testing - Separate src and test directories with Python - Stack Overflow
import unittest from src.classA import fetchData class classA(unittest.TestCase): def testStuff(self): ..testStuff.. ... Sign up to request clarification or add additional context in comments. ... # import the package import antigravity # import the antigravity module from antigravity import antigravity # or an object inside the antigravity module from antigravity.antigravity import my_object Β· But make sure that you added __init__.py files in each directory to make it a package (you can leave them empty)
🌐
Reddit
reddit.com β€Ί r/learnpython β€Ί how to import project source code from the test folder with unittest ?
r/learnpython on Reddit: How to import project source code from the test folder with unittest ?
November 13, 2023 -

Hi all !

I have a very classic projet with 2 folders :

src (which contains my source files)
test (which contains unit tests of my source code)

I would like my test folder to import modules from src folder to test them. But my problem is that I had to modify sys.path in each file of my test folder to make it work. Indeed I don't think it's a good technique because it entails a lot of code duplication and a lot of modification if I decide to change the arborescence in my test folder.

I feel like the right technique (don't hesitate to correct me if I'm wrong) would be to modify sys.path in one file only for the entire test folder. The problem is that my test folder contains a lot of subfolders and sub-subfolders. I tried to use relative import to import the source modules in my test files but I get this fairly classical Python error :

ImportError: attempted relative import with no known parent package

Anyone knows how to have a test folder outside the actual source code ? I've seen dozens of project that put the test folder outside the source code like me but I didn't find anything similar enough to my case to get inspiration (a project which uses unittest and which organize the code in several subfolders and sub-subfolders).

Thx in advance !

Top answer
1 of 1
1

Below is a first draft for a script that generates a unit test skeleton for missing test files. I asked AI to generate that script for me and refactored it a bit.

import inspect
import os


def main():
    src_dir = 'src'
    test_dir = 'test'
    generate_unit_test_skeleton(src_dir, test_dir)


def generate_unit_test_skeleton(src_dir, test_dir):
    # Loop over all folders and files in src directory
    for root, dirs, files in os.walk(src_dir):
        # Get the relative path of the current folder or file
        relative_folder_path = os.path.relpath(root, src_dir)

        # Create the corresponding unit test folder in test directory
        test_folder = generate_test_folder_if_not_exists(relative_folder_path, test_dir)

        # Loop over all files in the current folder
        for file in files:
            # Check if the file has a .py extension
            if file.endswith('.py'):
                generate_unit_tests_for_file(
                    file,
                    relative_folder_path,
                    test_folder,
                )


def generate_unit_tests_for_file(
    file,
    relative_directory,
    test_directory,
):
    # Create the corresponding unit test file in test directory
    generated_test_file_path = generate_unit_test_file(
        file,
        relative_directory,
        test_directory,
    )

    if generated_test_file_path is not None:
        # Get all classes and functions defined in the original file
        classes, functions = determine_members(file, relative_directory)

        # Generate test functions for each class and function
        generate_test_functions(
            file,
            generated_test_file_path,
            classes,
            functions,
        )


def generate_test_functions(
    file,
    test_file_path,
    classes,
    functions,
):
    module_name = determine_module_name(file)

    with open(test_file_path, 'a') as test_file:
        for class_name, class_instance in classes:
            generate_test_function_for_class(
                test_file,
                module_name,
                class_name,
                class_instance,
            )

        for function_name, function_instance in functions:
            generate_test_function_for_function(
                test_file,
                module_name,
                function_name,
                function_instance,
            )


def generate_test_function_for_function(
    test_file,
    module_name,
    function_name,
    function_instance,
):
    # Generate the test function name
    test_function_name = f'test_{function_name}'
    arguments = determine_arguments(function_instance)

    # Write the test function to the test file
    test_file.write(f'def {test_function_name}():\n')
    test_file.write(f'    # TODO: Implement test\n')
    test_file.write(f'    # result = {module_name}.{function_name}({arguments})\n')
    test_file.write(f'    pass\n')
    test_file.write('\n')


def generate_test_function_for_class(
    test_file,
    module_name,
    class_name,
    class_instance,
):
    # Generate the test function name
    test_function_name = f'test_{class_name}'

    arguments = determine_arguments(class_instance)

    # Write the test function to the test file
    test_file.write(f'def {test_function_name}():\n')
    test_file.write(f'    # TODO: Implement test\n')
    test_file.write(f'    # instance = {module_name}.{class_name}({arguments})\n')
    test_file.write(f'    pass\n')
    test_file.write('\n')


def determine_members(file, relative_directory):
    # Get the module name
    module_name = os.path.splitext(file)[0]

    # Import the module
    directory_import_path = relative_directory.replace('\\', '.')
    import_path = f'{directory_import_path}.{module_name}'
    module = __import__(import_path, fromlist=[module_name])

    # Get all classes and functions defined in the module
    classes = inspect.getmembers(module, inspect.isclass)
    functions = inspect.getmembers(module, inspect.isfunction)
    return classes, functions


def determine_arguments(function_instance):
    try:
        signature = inspect.signature(function_instance)
    except ValueError:
        return ''

    parameters = signature.parameters

    arguments = []
    for param in parameters.values():
        argument = determine_argument(param)
        arguments.append(argument)

    argument_string = ', '.join(arguments)
    if len(arguments) > 2:
        argument_string += ','  # leading comma causes line breaks if formatted with black
    return argument_string


def determine_argument(param):
    argument = param.name
    if param.default != inspect.Parameter.empty:
        default_value = determine_default_value(param.default)
        argument += f'={default_value}'
    return argument


def determine_default_value(default_instance):
    if inspect.isfunction(default_instance):
        return default_instance.__name__
    elif isinstance(default_instance, str):
        return f"'{default_instance}'"
    else:
        return default_instance


def generate_unit_test_file(file, relative_directory, test_directory):
    test_file_path = os.path.join(test_directory, f'test_{file}')
    if os.path.exists(test_file_path):
        return None
    else:
        # Open the test file in write mode
        with open(test_file_path, 'w') as f:
            # Write the initial import statement
            import_statement = generate_import_statement(file, relative_directory)
            f.write(import_statement)
            f.write('\n')
    return test_file_path


def generate_import_statement(file, relative_directory):
    directory_import_path = relative_directory.replace('////', '.')
    module_name = determine_module_name(file)
    statement = f'from {directory_import_path} import {module_name}\n'
    return statement


def determine_module_name(file):
    name = os.path.splitext(file)[0]
    return name


def generate_test_folder_if_not_exists(relative_path, test_dir):
    test_folder = os.path.join(test_dir, relative_path)
    if not os.path.exists(test_folder):
        os.makedirs(test_folder)
    return test_folder


if __name__ == '__main__':
    main()

Example result for a file 'test/foo/test_foo.py':

import foo.foo

def test_Language():
    # TODO: Implement test
    # instance = controls.Language(value, names=None, module=None, qualname=None, type=None, start=1, boundary=None,)
    pass

def test_Layout():
    # TODO: Implement test
    # instance = controls.Layout(kwargs)
    pass

def test_SimpleNamespace():
    # TODO: Implement test
    # instance = controls.SimpleNamespace()
    pass
Find elsewhere
🌐
Reddit
reddit.com β€Ί r/python β€Ί arguments against separating `test` from `src` in a python package?
r/Python on Reddit: Arguments against separating `test` from `src` in a python package?
March 23, 2023 -

The Python Packaging Authority recommends separating the test directory from the src (source code) directory in a Python application:

https://packaging.python.org/en/latest/tutorials/packaging-projects/#creating-the-package-files

Personally, I have always preferred this approach of keeping tests outside the package rather than mixing them with the source code (tests in package).

However, in the interest of expanding my perspective and learning something new, I am open to exploring alternative viewpoints. What are the main arguments for including tests within the package itself?

Image taken from https://blog.ionelmc.ro/2014/05/25/python-packaging/
🌐
Amazon S3
s3.amazonaws.com β€Ί assets.datacamp.com β€Ί production β€Ί course_15974 β€Ί slides β€Ί chapter3.pdf pdf
How to organize a growing set of tests?
UNIT TESTING FOR DATA SCIENCE IN PYTHON Β· Final test directory structure Β· src/ # All application code lives here Β· |-- data/ # Package for data preprocessing Β· | |-- __init__.py Β· | |-- preprocessing_helpers.py # Contains row_to_list(), convert_to_int() |-- features/ # Package for feature generation from preprocessed data Β·
🌐
GitHub
github.com β€Ί microsoft β€Ί vscode-python β€Ί issues β€Ί 11587
Debugging unit tests in a sub directory Β· Issue #11587 Β· microsoft/vscode-python
May 5, 2020 - "python.testing.unittestArgs": [ "-p", "*test.py" ], "python.testing.cwd": ".\\src", "python.testing.pytestEnabled": false, "python.testing.nosetestsEnabled": false, "python.testing.unittestEnabled": true
Author: microsoft
🌐
Reddit
reddit.com β€Ί r/learnpython β€Ί python unittest with py file in different directory
r/learnpython on Reddit: Python unittest with py file in different directory
August 25, 2023 -

Disclaimer: I initially posted this on r/learnprogramming but no one was responding, so I deleted that post and I'm posting this here. I also tried some googling (which I had tried prior to writing the post) and found both this post on r/learnpython and this post on StackOverflow. However, both speak about importing a Python file or module from the parent directory of there the Python test file is. I'm trying to do something a little different, although perhaps one of the answers given in those two posts would still apply. Anyway, here's my original post.

I'm using Python's unittest module to test my program. This is my first time using unittest. Currently I have my program py file in the same folder as the test py file. But I want to move them both into their own subfolders. As an example, this is my current setup:

\ root
|
|- my_program.py <- The program file
|
|- test.py       <- The unittest file

In test.py, my code looks similar to the following:

import unittest
import my_program

class TestMyProgram(unittest.TestCase):
   @classmethod
   def setupClass(cls):
      cls.PROGRAM = my_program

def test_method_one(self):
   expected_result = "Some result"
   self.assertEqual(self.PROGRAM.method_one(),expected_result)

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

As I mentioned though, I would like to reorganize my directory structure to the following:

\ root
|
|-- program_files     <- directory
|--|
|--|- my_program.py
|
|-- tests             <- directory
|--|
|--|- test.py

However, if I do that, what should my import statement look like to access my_program.py? Would it work if I used import package.module.pyfile? So, continuing my example above and using root as the project name, I would have something like import root.program_files.my_program. If it makes a difference, I'm using VS Code and running the test.py file using VS Code's run button.

🌐
Gideonbrimleaf
gideonbrimleaf.github.io β€Ί 2021 β€Ί 01 β€Ί 26 β€Ί relative-imports-python.html
Relative Imports in Python - Without Tearing Your Hair Out | Aiming to Misbehave
January 26, 2021 - import unittest from ..src.drink import Drink class TestDrink(unittest.TestCase): def setUp(self): self.my_awesome_drink = Drink("Americano", 450, "Coffee") def test_drink_has_name(self): self.assertEqual("Americano", self.my_awesome_drink.name) def test_drink_has_price(self): self.assertEqual(4.50, self.my_awesome_drink.price()) def test_drink_has_type(self): self.assertEqual("Coffee", self.my_awesome_drink.type) # We need the following so it will execute the tests when we run the file in python if __name__ == '__main__': unittest.main() As mentioned - Python 3.3 onwards can happily create namespaces for packages without an __init__.py file so we might think we’re all good to go. Let’s try and run our tests - for now let’s say we’re executing this whilst in the project directory (that’s going to matter real quick):
🌐
Plain English
plainenglish.io β€Ί home β€Ί blog β€Ί python β€Ί structuring unit tests in python
Structuring Unit Tests in Python
July 25, 2020 - The two alternatives are (1) to have a tests/ directory within the mpu/ package so that the tests get shipped with the code or (2) to put the test of a module next to the module. I would discourage (2) as I have never seen it for Python, but (1) is also commonly done and mentioned by pytest.
Top answer
1 of 5
13

If you're using py.test to run the tests, it won't find the application as you must have not installed it. You can use python -m pytest to run your tests, as python will auto-add your current working directory to your PATH.

Hence, using python -m pytest .. would work if you're doing it in the src directory

The structure that I normally use is:

root
β”œβ”€β”€ packagename
β”‚   β”œβ”€β”€ __init__.py
β”‚   └── ...
└── tests
    └── ...    

This way when I simply run python -m pytest in the root directory it works fine. Read more at: https://stackoverflow.com/a/34140498/1755083

Second option, if you still want to run this with py.test you can use:

PYTHONPATH=src/ py.test

and run it from the root. This simply adds the src folder to your PYTHONPATH and now your tests will know where to find the packagename package.

The third option is to use pip with -e to install your package in the editable mode. This creates a link to the packagename folder and installs it with pip - so any modification done in packagename is immediately reflected in pip's installation (as it is a link). The issue with this is that you need to redo the installation if you move the repo and need to keep track of which one is installed if you have more than 1 clones/packages.

2 of 5
7

Your layout looks OK and is the today recommended.

But I assume your problem are the import in your test_*.py files. You shouldn't take care about from where to import your package in you unittests; just import them.

Install in Development Mode via --editable

How could this be done? You have to "install" your package in Developement Mode. Use the --editable option of pip. In that case not a real package is build and installed but only symlinks are used to expose your package folder (the source in developer version) to the operating system as it would be a real release package.

Now your unittest never need to care about where the package is installed and how to import it. Just import it because the package is known to the system.

Note: This is the today recommended way. Other "solutions" hacking around with manipulating sys.path or environment variables like PYTHONPATH are just from Python's early days and should be avoided today.