Я пытаюсь протестировать созданный мной инструмент, который использует некоторые функции jMetalPy . У меня была / работает предыдущая версия, но сейчас я пытаюсь реорганизовать некоторые внешние зависимости (такие как вышеупомянутый jMetalPy).
Код проекта и структура
Вот минималистская структура моего project.
MyToolDirectory
¦--/MyTool
¦----/__init__.py
¦----/_jmetal
¦------/__init__.py
¦------/core
¦--------/quality_indicator.py
¦----/core
¦------/__init__.py
¦------/run_manager.py
¦----/tests
¦------/__init__.py
¦------/test_run_manager.py
Каталог _jmetal
предназначен для удаления внешней зависимости от пакета jMetalPy - и я скопировал только необходимые мне пакеты / модули.
Минимальное содержимое run_manager.py
# MyTool\core\run_manager.py
import jmetal
# from jmetal.core.quality_indicators import HyperVolume # old working version
class RunManager:
def __init__(self):
pass
@staticmethod
def calculate_hypervolume(front, ref_point):
if front is None or len(front) < 1:
return 0.
hv = jmetal.core.quality_indicator.HyperVolume(ref_point)
# hv = HyperVolume(ref_point)
hypervolume = hv.compute(front)
return hypervolume
Минимальное содержимое test_run_manager.py
# MyTool\tests\test_run_manager.py
import unittest
from unittest.mock import MagicMock, Mock, patch
from MyTool import core
class RunManagerTest(unittest.TestCase):
def setUp(self):
self.rm = core.RunManager()
def test_calculate_hypervolume(self):
ref_points = [0.0, 57.5]
front = [None, None]
# with patch('MyTool.core.run_manager.HyperVolume') as mock_HV: # old working version
with patch('MyTool.core.run_manager.jmetal.core.quality_indicator.HyperVolume') as mock_HV:
mock_HV.return_value = MagicMock()
res = self.rm.calculate_hypervolume(front, ref_points)
mock_HV.assert_called_with(ref_points)
mock_HV().compute.assert_called_with(front)
Основной вопрос
Когда я запускаю тест с кодом как есть, я получаю это сообщение об ошибке:
E ModuleNotFoundError: No module named 'MyTool.core.run_manager.jmetal'; 'MyTool.core.run_manager' is not a package
Но когда я изменяю его на:
with patch('MyTool.core.run_manager.jmetal.core') as mock_core:
mock_HV = mock_core.quality_indicator.HyperVolume
mock_HV.return_value = MagicMock()
res = self.rm.calculate_hypervolume(front, ref_points)
mock_HV.assert_called_with(ref_points)
mock_HV().compute.assert_called_with(front)
... теперь тест проходит. Что дает?!
Почему я не могу (или, скорее, как могу) я хирургически исправить тот класс, который я хочу (то есть HyperVolume
), не исправляя также весь подпакет? Есть ли способ обойти это? В jmetal.core
может быть код, который должен работать нормально.
Это причина того, что это не работает только потому, что в jMetalPy's jmetal \ core \ __ init __. Py * нет оператора from . import quality_indicator
. 1039 *? Потому что даже with patch('MyTool.core.run_manager.jmetal.core.quality_indicator)
throws:
E AttributeError: <module 'jmetal.core' from 'path\\to\\venv\\lib\\site-packages\\jmetal\\core\\__init__.py'> does not have the attribute 'quality_indicator'
Или я что-то не так делаю?
В случае, если речь идет только о добавлении этих операторов импорта, я мог бы сделайте это в моем подпакете _jmetal
, но я надеялся позволить пользователю по умолчанию использовать собственную установку jMetalPy, если у него уже была такая, добавив ее в MyTool \ __ init__.py:
try:
import jmetal
except ModuleNotFoundError:
from . import _jmetal as jmetal
и затем заменить все экземпляры import jmetal
на from MyTool import jmetal
. Тем не менее, я столкнулся бы с той же проблемой снова и снова.
Я чувствую, что есть какая-то основная концепция, которую я не понимаю. Спасибо за помощь.