При тестировании кода с глобальными переменными или синглтонами, важно изолировать тестируемый модуль от нежелательных побочных эффектов.
Основные подходы:
unittest.mock
(или pytest-mock
): Мок-объекты позволяют заменить глобальные переменные/синглтоны временными "заглушками" в рамках теста.patch
декоратор/контекстный менеджер: Удобный способ временно заменить атрибут модуля или класса моком. @patch('module.global_variable', new_value=mock_object)
reset()
).Пример (с использованием unittest.mock
):
import unittest
from unittest.mock import patch
# Модуль с глобальной переменной
import my_module
class TestMyModule(unittest.TestCase):
@patch('my_module.global_variable', 'mocked_value')
def test_my_function(self, mock_global_variable):
# Теперь my_module.global_variable внутри теста - 'mocked_value'
result = my_module.my_function()
self.assertEqual(result, expected_result)
# mock_global_variable - сам mock-объект, можно проверять вызовы:
# mock_global_variable.assert_called_once()
Важно помнить о восстановлении исходного состояния после теста, чтобы избежать влияния на другие тесты.
Тестирование кода, зависящего от глобальных переменных или синглтонов, представляет собой сложность из-за их глобального состояния. Мок-объекты могут помочь в изоляции тестируемого кода и контроле над этим состоянием. Вот несколько стратегий:
1. Рефакторинг кода для внедрения зависимостей: Лучший подход - избегать прямого использования глобальных переменных или синглтонов внутри тестируемого кода. Вместо этого, передавайте эти зависимости как аргументы в функции или конструкторы классов. Это значительно упрощает мокирование.
# Вместо:
global_config = ...
def my_function():
if global_config.debug_mode:
print("Debug mode enabled")
# Используйте:
def my_function(config):
if config.debug_mode:
print("Debug mode enabled")
# Тогда в тесте:
from unittest.mock import MagicMock
mock_config = MagicMock()
mock_config.debug_mode = True
my_function(mock_config)
2. Мокирование глобальных переменных напрямую: Если рефакторинг невозможен, можно использовать unittest.mock.patch
для временной замены глобальной переменной мок-объектом в рамках теста. Это работает только если глобальная переменная доступна для изменения (то есть, она не защищена от записи).
import unittest
from unittest.mock import patch
global_variable = ...
def function_using_global():
return global_variable.some_method()
class MyTest(unittest.TestCase):
@patch('your_module.global_variable') # Замените your_module на имя модуля
def test_function_using_global(self, mock_global):
mock_global.some_method.return_value = "mocked value"
result = function_using_global()
self.assertEqual(result, "mocked value")
mock_global.some_method.assert_called_once()
3. Мокирование методов синглтона: Аналогично глобальным переменным, можно мокировать методы экземпляра синглтона с помощью patch.object
. Сначала нужно получить экземпляр синглтона, а затем пропатчить нужный метод.
import unittest
from unittest.mock import patch
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
def get_data(self):
return "Real data"
def function_using_singleton():
singleton = Singleton()
return singleton.get_data()
class MyTest(unittest.TestCase):
@patch.object(Singleton, 'get_data')
def test_function_using_singleton(self, mock_get_data):
mock_get_data.return_value = "Mocked data"
result = function_using_singleton()
self.assertEqual(result, "Mocked data")
mock_get_data.assert_called_once()
4. Контекстные менеджеры и фикстуры: Можно использовать контекстные менеджеры или фикстуры (например, в pytest) для автоматической замены глобальных переменных или синглтонов в начале теста и восстановления их исходного состояния по окончании. Это помогает избежать побочных эффектов между тестами.
Важно:
В заключение, мокирование глобальных переменных и синглтонов возможно, но рекомендуется избегать прямого использования этих практик, если это возможно. Рефакторинг кода для внедрения зависимостей – лучший путь к более тестируемому и поддерживаемому коду.