Modules and submodules (unittest)

"I am currently a Software Engineering student at ALX. I'm passionate about technology and enjoy conducting research to find answers on my own. I have a natural inclination to ask 'WHY' more often than 'HOW'.
"While working on projects at ALX, I have acquired a wealth of interesting and diverse knowledge about software engineering and computer science in general. Therefore, I needed a place to store and save all this information, allowing me to refer back to it whenever I forget."
Let's say we have the following directory structure:
main_module/
├── moduleA.py
└── submodules/
├── submoduleB.py
└── submoduleC.py
Inside moduleA.py, we define a function called hello():
# moduleA.py
def hello():
print("Hello from moduleA!")
Inside submoduleB.py, we define a function called greet():
# submoduleB.py
def greet():
print("Greetings from submoduleB!")
Now, let's use moduleA and submoduleB together in a program:
# main.py
import main_module.moduleA as moduleA
import main_module.submodules.submoduleB as submoduleB
moduleA.hello() # Output: Hello from moduleA!
submoduleB.greet() # Output: Greetings from submoduleB!
In this example, moduleA.py is a module, and submoduleB.py is a submodule within the main_module package. We can import them using dot notation to access their functions and use them in our program. The program will output the respective messages defined in each module/submodule.
This illustrates how modules and submodules can be organized and imported to reuse code in a hierarchical manner.
Lets look at a real example:
hierarchy for the unittest package and its submodules:
unittest/
├── __init__.py
├── case.py
├── loader.py
├── main.py
├── mock.py
├── result.py
├── runner.py
├── signals.py
├── suite.py
└── util.py
unittest(package): The top-level package that contains all the testing-related modules and submodules.__init__.py: An empty file that marks the directory as a package and allows importing from it. Not necessarily empty in this case.case.py: Module containing theTestCaseclass, which is the base class for defining individual test cases. It provides a set of assertion methods and hooks for setting up and tearing down test fixtures.loader.py: Module providing test case discovery and loading mechanisms.main.py: Module providing the main entry point for running tests.mock.py: Module containing classes and functions for creating and working with mock objects during testing.result.py: Module containing classes for storing and reporting test results.runner.py: Module containing classes for running test suites and test cases.signals.py: Module containing classes for managing signals during test runs.suite.py: Module containing classes for managing collections of test cases and test suites.util.py: Module containing various utility functions and classes used internally by theunittestframework.
In this hierarchy, the TestCase class is located in the case.py module, and the Mock class is located in the mock.py module.
Since TestCase class is implemented inside the case module, If you have written python tests before, you would probably be wondering how we have been able to do this unittest.TestCase
In the unittest package, the TestCase class is defined in the case.py module. When you import the unittest package, the TestCase class is made available as an attribute of the unittest module. This is due to the way the __init__.py file of the unittest package is implemented.
The __init__.py file of a package can define what gets imported when the package is imported. In the case of the unittest package, the __init__.py file imports the TestCase class from the case module and makes it accessible as an attribute of the unittest module itself.
So, when you import unittest, you can access the TestCase class using the syntax unittest.TestCase. This is because the TestCase class is imported and made available within the unittest package's namespace.
This will give you an idea of what might be found in the unittest/__init__.py
# Importing commonly used classes and functions for convenience
from .case import TestCase
from .loader import TestLoader
from .main import main
from .mock import patch
from .result import TestResult, TextTestResult
from .runner import TextTestRunner
from .signals import installHandler
from .suite import TestSuite
# Importing subpackages and modules within the unittest package
import unittest.mock
import unittest.signals
import unittest.util
# Other initialization code or configuration
# Exporting symbols to be accessible when importing the unittest package
__all__ = [
'TestCase',
'TestLoader',
'main',
'patch',
'TestResult',
'TextTestResult',
'TextTestRunner',
'installHandler',
'TestSuite',
# Other symbols to export if applicable
]
# Additional code or configurations
If you have the unittest package installed on your system, you can locate and examine the actual __init__.py file within the package to see the precise code that is used in your specific environment.
The __all__ variable is a list that specifies the symbols (functions, classes, variables) that should be imported or made accessible when someone imports a module or a package



