I have a repository that contains multiple programs:
.
└── Programs
├── program1
│ └── Generic_named.py
└── program2
└── Generic_named.py
I would like to add testing to this repository.
I have attempted to do it like this:
.
├── Programs
│ ├── program1
│ │ └── Generic_named.py
│ └── program2
│ └── Generic_named.py
└── Tests
├── mock
│ ├── 1
│ │ └── custom_module.py
│ └── 2
│ └── custom_module.py
├── temp
├── test1.py
└── test2.py
Where temp
is a folder to store each program temporarily with mock versions of any required imports that can not be stored directly with the program.
Suppose we use a hello world example like this:
cat Programs/program1/Generic_named.py
import custom_module
def main():
return custom_module.out()
cat Programs/program2/Generic_named.py
import custom_module
def main():
return custom_module.out("Goodbye, World!")
cat Tests/mock/1/custom_module.py
def out():return "Hello, World!"
cat Tests/mock/2/custom_module.py
def out(x):return x
And I were to use these scripts to test it:
cat Tests/test1.py
import unittest
import os
import sys
import shutil
if os.path.exists('Tests/temp/1'):
shutil.rmtree('Tests/temp/1')
shutil.copytree('Tests/mock/1', 'Tests/temp/1/')
shutil.copyfile('Programs/program1/Generic_named.py', 'Tests/temp/1/Generic_named.py')
sys.path.append('Tests/temp/1')
import Generic_named
sys.path.remove('Tests/temp/1')
class Test(unittest.TestCase):
def test_case1(self):
self.assertEqual(Generic_named.main(), "Hello, World!")
if __name__ == '__main__':
unittest.main()
cat Tests/test2.py
import unittest
import os
import sys
import shutil
if os.path.exists('Tests/temp/2'):
shutil.rmtree('Tests/temp/2')
shutil.copytree('Tests/mock/2', 'Tests/temp/2')
shutil.copyfile('Programs/program2/Generic_named.py', 'Tests/temp/2/Generic_named.py')
sys.path.append('Tests/temp/2')
import Generic_named
sys.path.remove('Tests/temp/2')
class Test(unittest.TestCase):
def test_case1(self):
self.assertEqual(Generic_named.main(), "Goodbye, World!")
if __name__ == '__main__':
unittest.main()
Both tests pass when run individually:
python3 -m unittest Tests/test1.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
python3 -m unittest Tests/test2.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
However, they fail when being run together:
python3 -m unittest discover -p test*.py -s Tests/
.F
======================================================================
FAIL: test_case1 (test2.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/s/Documents/Coding practice/2024/Test Mess/1/Tests/test2.py", line 18, in test_case1
self.assertEqual(Generic_named.main(), "Goodbye, World!")
AssertionError: 'Hello, World!' != 'Goodbye, World!'
- Hello, World!
+ Goodbye, World!
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
If I try to use a different temporary name for one of the scripts I am trying to test,
cat Tests/test2.py
import unittest
import os
import sys
import shutil
if os.path.exists('Tests/temp/2'):
shutil.rmtree('Tests/temp/2')
shutil.copytree('Tests/mock/2', 'Tests/temp/2')
shutil.copyfile('Programs/program2/Generic_named.py', 'Tests/temp/2/Generic_named1.py')
sys.path.append('Tests/temp/2')
import Generic_named1
sys.path.remove('Tests/temp/2')
class Test(unittest.TestCase):
def test_case1(self):
self.assertEqual(Generic_named1.main(), "Goodbye, World!")
if __name__ == '__main__':
unittest.main()
Then I get a different error:
python3 -m unittest discover -p test*.py -s Tests/
.E
======================================================================
ERROR: test_case1 (test2.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/s/Documents/Coding practice/2024/Test Mess/2/Tests/test2.py", line 18, in test_case1
self.assertEqual(Generic_named1.main(), "Goodbye, World!")
File "/home/s/Documents/Coding practice/2024/Test Mess/2/Tests/temp/2/Generic_named1.py", line 4, in main
return custom_module.out("Goodbye, World!")
TypeError: out() takes 0 positional arguments but 1 was given
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (errors=1)
It seems to be trying to import the same file, despite me using a different file from a different path with the same name. This seems strange, as I’ve been making sure to undo any changes to the Python Path after importing what I wish to test. Is there any way to mock the path? I can’t change the name of the custom_module
, as that would require changing the programs I wish to test.
How should I write, approach, or setup these tests such that they can be tested with unittest discover the same as they can individually?
The “why” is that the import system is caching modules in
sys.modules
.The “what to do about it” is “not this”. Use a package layout with explicit names (
p1.generic_name
etc) instead.You can use relative imports under those packages if you prefer (
from .generic_name import ...
).If you want executables on the path look at setup.py or any of the myriad of overlapping modern equivalents that’ll let you specify a command-libe executable to install, then
pip install -e .
to install it in your venv’s bin dir.