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?
Since you have all your
shutil.copytree
s andsys.path
manipulation at the top level of the test modules, they are executed the moment those modules are imported.unittest
likely imports all discovered test modules before actually executing the tests so the set up of both modules is executed in random order before the tests are run. The correct way to perform test setup is usingsetUp
andsetUpClass
methods ofunittest.TestCase
. Their counterpartstearDown
andtearDownClass
are used to clean up after tests. You probably will be able to get this to work somehow using those methods.However, I’m fairly certain that this entire question is an example of the XY problem and you should be approaching this whole thing differently. Copying the modules and their mock dependencies into a temporary directory and manipulating
sys.path
seems like an absolute nightmare and it will be a massive PITA even if you get it to a working state. I don’t know what problem exactly you’re trying to solve but I think you should really read up onunittest.mock
and even more importantly on dependency injection.