pythonpython-importpython-unittest

How to fix discovery of module-under-test in test code with unittest?


I've been struggling with this error and I have no idea of why it happens.

ModuleNotFoundError: No module named 'lambdaFunction'

This is the folder structure.

Folder Structure

And for some reason when I try to run the lambda_unit_test.py it keeps failing.

This is the code in there:

import unittest
from lambdaFunction import index


class LambdaTest(unittest.TestCase):
    def lambda_returns_Rick_Sanchez(self):
        self.assertEquals(index.lambda_handlerx(), "Should return Rick Sanchez")


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

I've also tried things like import lambdaFunction without any luck.

These are the command line outputs:

python3 -m unittest test.lambda_unit_test.py
E
======================================================================
ERROR: lambda_unit_test (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lambda_unit_test
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/unittest/loader.py", line 154, in loadTestsFromName
    module = __import__(module_name)
ModuleNotFoundError: No module named 'test.lambda_unit_test'


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

If I try running python3 -m unittest test.lambda_unit_test from the root folder it does not run any test:

python3 -m unittest test.lambda_unit_test  

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Any ideas on how to fix this?


Solution

  • There are 2 problems.

    Problem 1

    The 1st problem is the "ModuleNotFoundError: No module named 'lambdaFunction'", which is usually caused by how you are running unittest. As described in Running unittest with typical test directory structure (specifically, this nice answer), you have to make sure unittest finds both your modules-under-test and the test files.

    So, given

    myproject
    ├── lambdaFunction
    │   ├── __init__.py
    │   └── index.py
    └── tests
        ├── __init__.py
        └── lambda_unit_test.py
    

    You run unittest under the myproject folder:

    $ cd myproject
    $ python3 -m unittest discover
    

    That would tell unittest to use its Test Discovery feature to find your test files and test methods. That would also automatically fix your import paths (sys.path), so that your tests would be able to import your modules, without you needing to hack around sys.path.

    If the discovery works correctly, then from lambdaFunction import index should be fine, without any modifications to sys.path, which can easily break when renaming and moving around files and folders.

    Problem 2

    The 2nd problem is naming, specifically the names of your test files and names of your test methods. They don't follow the default patterns used by TestLoader.discover() which is used in Test Discovery:

    1. lambda_unit_test.py

    2. lambda_returns_Rick_Sanchez

      • By default, unittest looks for methods that start with test (ex. test_function).
      • As to why, see the testMethodPrefix attribute of unittest's TestLoader class, which is a "string giving the prefix of method names which will be interpreted as test methods. The default value is 'test'."

    If you just run python3 -m unittest discover as is, expect to get "Ran 0 tests". You can specify the exact test to run, skipping the Test Discovery pattern problems, by passing in test_module.TestClass.test_method:

    $ python3 -m unittest test.lambda_unit_test.LambdaTest.lambda_returns_Rick_Sanchez
    

    But that's not a good solution. One option to fix this is to simply follow the default patterns used by Test Discovery. Rename your test files and test methods like so:

    1. lambda_unit_test.py --> test_lambda_functions.py
    2. lambda_returns_Rick_Sanchez --> test_lambda_returns_Rick_Sanchez
    $ python3 -m unittest discover -v
    test_lambda_returns_Rick_Sanchez (test.test_lambda_functions.LambdaTest) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    If you really want to use custom test file names and/or test method names, you will have to customize the test loader. See the load_tests Protocol section of the docs:

    Modules or packages can customize how tests are loaded from them during normal test runs or test discovery by implementing a function called load_tests.

    There are a number of ways to implement load_tests, one way is to add a load_tests function in your test/__init__.py file, as instructed in the load_tests Protocol:

    If discovery is started in a directory containing a package, either from the command line or by calling TestLoader.discover(), then the package __init__.py will be checked for load_tests.

    # In test/__init__.py
    
    from pathlib import Path
    from unittest import TestSuite
    
    def load_tests(loader, tests, pattern):
        suite = TestSuite()
        loader.testMethodPrefix = 'lambda_returns'
        tests = loader.discover(
            Path(__file__).parent.as_posix(),
            pattern='*_test.py',
        )
        suite.addTests(tests)
        return suite
    

    That code changes the method name prefix for tests to lambda_returns and to look for files with pattern *_test.py, both overriding the default behavior.

    With that it should finally find your tests:

    $ python3 -m unittest discover -v
    lambda_returns_Rick_Sanchez (test.lambda_unit_test.LambdaTest) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK
    

    If you can, I highly recommend following instead the default patterns for test files and test method names. It's clearer (to me at least) and less code to maintain, the better.

    As a side note, self.assertEquals is now deprecated. You should use instead assertEqual.