How should I deal with multiple imports having the same name? - eviltoast

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?

  • gedhrel@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    4 months ago

    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.