Тестирование кода требует особого подхода, когда речь идёт о компонентах, взаимодействующих с внешним миром. Мы часто сталкиваемся с непредсказуемостью HTTP-запросов, чтением данных из базы или файловой системы. В этом нам и может помочь мокинг - техника, позволяющая заменять реальные объекты их имитациями в тестовой среде.
Такая ситуация: вы тестируете функцию, которая обращается к внешнему API для получения данных о погоде. Эта функция работает отлично, когда API доступен. Но что произойдёт, если сервис временно недоступен? Или если вы запускаете тесты на машине без интернета? Ваши тесты станут ненадёжными, будут периодически падать, и весь процесс непрерывной интеграции может оказаться под угрозой.
В Python бибилиотека unittest.mock предоставляет разнообразные инструменты для создания имитаций (моков) объектов. Мок-объект - это своеобразный "актёр", способный сыграть роль практически любого другого объекта. При этом он записывает информацию о том, как с ним взаимодействовали: какие методы вызывали, с какими аргументами и как часто. Отсутствие моков в тестах создаёт ряд серьёзных проблем:
1. Нестабильность тестов из-за зависимости от внешних сервисов.
2. Сложность воспроизведения редких ситуаций и ошибок.
3. Медленное выполнение тестов из-за реальных сетевых запросов или операций с базой данных.
4. Ограниченное покрытие кода, особенно обработчиков исключений.
Для понимания места моков в экосистеме тестирования стоит различать их от смежных концепций. Многие разработчики путают моки со стабами и фейками, хотя между ними есть фундаментальные различия.
Мок-объект не только имитирует поведение, но и верифицирует ожидаемые взаимодействия. С его помощью можно проверить, вызывался ли конкретный метод с определёнными параметрами.
Стаб гораздо проще - это заглушка, возвращающая предопределённые ответы на определённые вызовы. Стабы не отслеживают, как их использовали, и не проверяют правильность взаимодействия.
Фейк - полноценная рабочая реализация, но упрощённая версия реального объекта. Например, вместо реальной базы данных можно использовать in-memory хранилище.
Моки играют ключевую роль в методологии разработки через тестирование (TDD). При TDD сначала пишется тест, который проверяет ещё не существующую функциональность. На этом этапе моки незаменимы - они позволяют абстрагироваться от реализации и сосредоточиться на интерфейсах и взаимодействиях компонентов. Разработчик может определить, как будущий код должен взаимодействовать с другими частями системы, не реализуя его.
Основы библиотеки unittest.mock
Библиотека unittest.mock появилась в стандартной библиотеке Python начиная с версии 3.3. Для более ранних версий разработчикам приходилось использовать сторонний пакет mock, который затем был интегрирован в официальную поставку языка. Библиотека представляет собой набор инструментов для создания контролируемых объектов-заменителей, которые позволяют симулировать поведение практически любых компонентов системы. Центральным элементом библиотеки выступает класс Mock – универсальный имитатор, способный принять облик практически любого объекта в вашей системе. Уникальность Mock проявляется в его "ленивой" природе создания атрибутов. Если вы обращаетесь к атрибуту или методу, которого ранее не существовало, Mock автоматически создаст его для вас:
| Python | 1
2
3
4
5
6
7
8
9
10
| from unittest.mock import Mock
# Создаём простой мок-объект
database = Mock()
# Обращаемся к несуществующему методу - он создаётся автоматически
database.connect()
# Можем проверить, был ли вызван этот метод
database.connect.assert_called_once() |
|
Эта способность динамически генерировать интерфейс делает Mock невероятно гибким инструментом, но одновременно может стать источником проблем – опечатка в названии метода не вызовет ошибки, так как мок просто создаст новый атрибут. В семействе моков есть и более специализированный класс – MagicMock. Он расширяет базовый функционал Mock, добавляя реализации распространённых магических методов Python:
| Python | 1
2
3
4
5
6
7
8
9
| from unittest.mock import MagicMock
# Создаём MagicMock
advanced_mock = MagicMock()
# MagicMock уже имеет реализации для магических методов
length = len(advanced_mock) # Работает без ошибок
result = advanced_mock + 5 # Тоже работает
item = advanced_mock["key"] # И это тоже |
|
При использовании обычного Mock любое из этих действий вызвало бы ошибку, поскольку по умолчанию магические методы (начинающиеся с двойного подчёркивания) не создаются автоматически. MagicMock предпочтительнее использовать, когда тестируемый код может взаимодействовать с объектом разнообразными способами, особенно если задействованы операторы или стандартные функции Python.
Один из ключевых аспектов работы с моками – настройка их поведения. Каждый мок может быть тонко сконфигурирован для имитации различных сценариев работы. Наиболее часто используемые параметры конфигурации:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # Настройка возвращаемого значения
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"data": "some value"}
# При вызове json() будет возвращен указанный словарь
result = mock_response.json() # {"data": "some value"}
# Более сложная настройка при инициализации
complex_mock = Mock(
name="database_connection", # Имя для лучшей читаемости в отчётах
return_value=True, # Что вернёт сам мок при вызове
side_effect=KeyError, # Или какое исключение выбросит
) |
|
Метод .configure_mock() предоставляет альтернативный способ настройки, особенно полезный для конфигурирования вложенных атрибутов:
| Python | 1
2
3
4
5
6
| response_mock = Mock()
response_mock.configure_mock(**{
"status_code": 200,
"headers.content_type": "application/json",
"json.return_value": {"data": "value"}
}) |
|
Одной из самых пагубных проблем при использовании моков является их чрезмерная гибкость – они позволяют ошибкам в интерфейсе объектов оставаться незамеченными. Например, если в реальном коде метод переименовали с get_user() на get_profile(), тесты с моками продолжат работать, но уже будут проверять несуществующий функционал. Для решения этой проблемы существует механизм autospec. Он заставляет мок строго следовать интерфейсу оригинального объекта:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| from unittest.mock import create_autospec
# Предположим, у нас есть класс с определённым интерфейсом
class Database:
def connect(self):
pass
def execute_query(self, query):
pass
# Создаём мок с автоспецификацией
db_mock = create_autospec(Database)
# Этот вызов сработает, так как метод существует
db_mock.connect()
# А этот вызовет AttributeError, потому что такого метода нет
try:
db_mock.execute() # Ошибка!
except AttributeError:
print("Метод не найден в интерфейсе") |
|
Альтернативный способ – передать параметр spec или spec_set при создании мока:
| Python | 1
2
| db_mock = Mock(spec=Database) # Мягкая проверка интерфейса
db_mock_strict = Mock(spec_set=Database) # Строгая проверка, запрещающая установку новых атрибутов |
|
Отслеживание вызовов мок-объектов – ещё одна важная функциональность библиотеки. У каждого мока автоматически записывается история взаимодействий, которую можно проанализировать:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| service = Mock()
service.get_data(1, category="books")
service.get_data(2, category="movies")
# Проверяем количество вызовов
assert service.get_data.call_count == 2
# Последний вызов
last_call = service.get_data.call_args
print(last_call) # call(2, category='movies')
# Все вызовы в виде списка
all_calls = service.get_data.call_args_list
print(all_calls) # [call(1, category='books'), call(2, category='movies')] |
|
Класс call используется для создания объектов, представляющих вызовы функций. Это особенно полезно при написании сложных проверок:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| from unittest.mock import call
# Проверка конкретного вызова в списке
expected_call = call(1, category="books")
assert expected_call in service.get_data.call_args_list
# Проверка последовательности вызовов
expected_sequence = [
call(1, category="books"),
call(2, category="movies")
]
assert service.get_data.call_args_list == expected_sequence |
|
Библиотека unittest.mock также предоставляет множество специализированных методов для проверки вызовов, таких как assert_called(), assert_called_once(), assert_called_with(), assert_any_call() и другие. Каждый из них позволяет провести определённый тип проверки без необходимости вручную анализировать историю вызовов.
Понимание основ библиотеки unittest.mock – первый шаг к написанию качественных и надёжных тестов. В арсенале Python-разработчика эти инструменты становятся незаменимыми, когда требуется изолировать тестируемый код от внешних зависимостей или смоделировать сложные сценарии взаимодействия компонентов системы.
Unittest - mock подменяющий __init__ и возвращающий type исходного объекта Добрый день! Я хочу, чтобы в метод передавалась переменная только определенного класса. Для этого я проверяю внутри метода на typeof
Теперь мне... Mock с массивами Всем привет.
Нужно замокать репозиторий, при этом необходимо чтобы первый метод принимал массив, второй его модифицировал, а третий выводил.
... Mock для еще не написанного API По логике приложения внешнее REST API должно возвращать набор объектов, которые я передаю на клиентскую сторону также по REST. Нужно как-то получать... Mock in Unittest Доброго времени суток! Во время написания теста столкнулся с проблемой. На сильно упрощённом примере, приведённом ниже, попробую объяснить. Есть...
Техники мокинга в Python
После знакомства с основами библиотеки unittest.mock самое время погрузиться в практические техники мокирования. Центральным инструментом для подмены объектов в тестах выступает функция patch(). Она позволяет временно заменять реальные объекты их имитациями, обеспечивая полный контроль над поведением компонентов системы. Функция patch() настолько универсальна, что может использоваться как декоратор, менеджер контекста или напрямую в коде:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| from unittest.mock import patch
# Как декоратор для функции
@patch('module.ClassName')
def test_something(mock_class):
# mock_class автоматически передаётся в аргументы
instance = mock_class.return_value
# Тестовый код...
# Как декоратор для метода класса
class TestCase:
@patch('module.function')
def test_method(self, mock_function):
# Тестовый код...
# Как менеджер контекста
def test_with_context():
with patch('module.function') as mock_func:
# mock_func доступен только внутри этого блока
result = some_code_that_calls_function()
mock_func.assert_called_once() |
|
Ключевой нюанс при использовании patch() – правильное указание пути к замещаемому объекту. Необходимо указывать путь относительно модуля, где объект используется, а не где он определён:
| Python | 1
2
3
4
5
6
7
8
9
10
| # В файле service.py
from external import api_client
def get_data():
return api_client.fetch()
# В тестовом файле test_service.py
@patch('service.api_client') # Патчим относительно service, а не external!
def test_get_data(mock_client):
# Тестовый код... |
|
Настройка возвращаемых значений – основной способ контролировать поведение мок-объектов. Простейший вариант – установка атрибута return_value:
| Python | 1
2
3
4
5
6
7
8
| def test_database_query():
with patch('app.database.execute_query') as mock_query:
# Указываем, что должен вернуть мок при вызове
mock_query.return_value = [{'id': 1, 'name': 'Test'}]
# Тестируемый код будет получать этот результат
result = app.get_user_data()
assert result[0]['name'] == 'Test' |
|
Более гибкий механизм – использование side_effect. Этот атрибут можно настроить несколькими способами:
1. Передать исключение, которое будет выброшено при вызове:
| Python | 1
| mock_function.side_effect = ValueError("Недопустимый аргумент") |
|
2. Передать функцию, которая будет вызвана с теми же аргументами:
| Python | 1
2
3
4
5
6
| def custom_behavior(arg):
if arg < 0:
raise ValueError("Отрицательные числа не поддерживаются")
return arg * 2
mock_function.side_effect = custom_behavior |
|
3. Передать итерируемый объект, элементы которого будут возвращаться при последовательных вызовах:
| Python | 1
2
3
4
5
| # Первый вызов вернёт 1, второй - 2, третий - 3
mock_function.side_effect = [1, 2, 3]
# Или первый вызов вернёт значение, а второй выбросит исключение
mock_function.side_effect = [{'data': 'value'}, Exception("Ошибка сети")] |
|
Для тестирования исключений side_effect незаменим. Он позволяет проверить, что код корректно обрабатывает ошибки:
| Python | 1
2
3
4
5
6
7
| def test_error_handling():
with patch('app.api_client.get_data') as mock_get:
mock_get.side_effect = ConnectionError("Невозможно установить соединение")
# Проверяем, что наш код правильно обрабатывает исключение
result = app.fetch_data_safely()
assert result == {'status': 'error', 'message': 'Сервис недоступен'} |
|
Мокирование целых классов требует особого подхода, особенно когда необходимо контролировать поведение создаваемых экземпляров:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def test_class_instance():
# Патчим сам класс
with patch('app.ApiClient') as MockApiClient:
# Настраиваем, что будет возвращать конструктор класса
mock_instance = MockApiClient.return_value
# Теперь можно настроить методы этого экземпляра
mock_instance.connect.return_value = True
mock_instance.get_data.return_value = {'status': 'ok'}
# Тестируемый код, который создаёт экземпляр ApiClient
result = app.process_api_data()
# Проверяем ожидаемое взаимодействие
MockApiClient.assert_called_once()
mock_instance.connect.assert_called_once()
assert result['status'] == 'ok' |
|
При работе со сложными вложенными объектами часто возникает необходимость настраивать цепочки вызовов. Для этого можно использовать точечную нотацию в строках конфигурации:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| def test_nested_objects():
mock_response = Mock()
# Настраиваем сложную структуру одной командой
mock_response.configure_mock(**{
'json.return_value.data.items.return_value': [('key', 'value')],
'status_code': 200
})
# Теперь цепочка вызовов вернёт ожидаемый результат
with patch('app.requests.get', return_value=mock_response):
result = app.fetch_items()
assert result == [('key', 'value')] |
|
Наиболее точный контроль над мокированием отдельных атрибутов объекта предоставляет функция patch.object(). Она позволяет заменить конкретный метод или свойство, сохраняя остальную функциональность:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| from unittest.mock import patch
def test_specific_method():
# Патчим только метод calculate класса Calculator
with patch.object(Calculator, 'calculate', return_value=42):
calc = Calculator()
# Все остальные методы работают как обычно
calc.initialize() # Вызов реального метода
# А этот метод замокирован
result = calc.calculate(10, 20)
assert result == 42 |
|
Различные сценарии тестирования часто требуют комбинированного подхода к мокированию. Рассмотрим несколько специфических техник, которые особенно полезны при создании сложных тестов.
Для мокирования операций с файловой системой, patch() можно применять к встроенным функциям, таким как open:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| def test_file_reading():
# Имитируем открытый файл с содержимым
mock_file = mock_open(read_data="тестовая строка")
with patch("builtins.open", mock_file):
# Код, который читает файл, получит наши данные
content = app.read_config_file()
# Проверяем, что файл был открыт правильно
mock_file.assert_called_once_with("config.json", "r")
assert content == "тестовая строка" |
|
При тестировании методов, возвращающих генераторы или итераторы, side_effect можно использовать для создания собственных итерируемых объектов:
| Python | 1
2
3
4
5
6
7
8
9
10
| def test_data_processing():
# Создаём генератор, который будет возвращать наши тестовые данные
def mock_data_generator():
yield {"id": 1, "name": "Элемент 1"}
yield {"id": 2, "name": "Элемент 2"}
with patch("app.fetch_data_stream", side_effect=mock_data_generator):
results = list(app.process_data_stream())
assert len(results) == 2
assert results[0]["processed"] == True |
|
Отдельного внимания заслуживает вопрос мокирования декораторов. Поскольку декораторы обычно заменяют или оборачивают функции при определении, простой патч не всегда эффективен. В таких случаях можно применить двухступенчатый подход:
| Python | 1
2
3
4
5
6
7
8
9
10
| def test_decorated_function():
# Первый патч для самого декоратора
with patch("app.authentication.require_auth", lambda func: func):
# Второй патч для основной функции
with patch("app.api.get_user_data") as mock_get:
mock_get.return_value = {"name": "Тестовый пользователь"}
# Теперь вызов пройдёт без аутентификации
result = app.api.get_user_profile(123)
assert result["name"] == "Тестовый пользователь" |
|
Глубокое мокирование объектов баз данных часто становится критически важным, особенно при работе с ORM, такими как SQLAlchemy или Django ORM:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| def test_database_operations():
# Создаём имитацию объекта сессии БД
mock_session = Mock()
# Настраиваем имитацию запроса
mock_query = mock_session.query.return_value
mock_filter = mock_query.filter.return_value
mock_first = mock_filter.first
# Указываем, что запрос должен вернуть
user_record = Mock(id=1, username="testuser", is_active=True)
mock_first.return_value = user_record
with patch("app.db.session", mock_session):
user = app.get_user_by_username("testuser")
# Проверяем, что запрос был построен корректно
mock_session.query.assert_called_once()
mock_query.filter.assert_called_once()
assert user.username == "testuser" |
|
Для тестирования потенциально опасного кода, который может внести изменения в систему, сочетание моков с контекстными менеджерами особенно эффективно:
| Python | 1
2
3
4
5
6
7
8
9
| def test_system_operations():
# Мокируем различные системные операции
with patch("os.remove"):
with patch("app.logger.warn") as mock_warn:
# Код, который выполняет потенциально опасные операции
app.cleanup_temp_files()
# Проверяем, что логгер был использован правильно
mock_warn.assert_called_with("Удаление временных файлов") |
|
Тестирование параллельных операций с использованием моков требует особого внимания к последовательности вызовов. Функция call_args_list помогает контролировать порядок и содержание вызовов:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| def test_parallel_operations():
mock_worker = Mock()
with patch("app.Worker", return_value=mock_worker):
app.process_batch([1, 2, 3])
# Проверяем, что все элементы были обработаны
assert mock_worker.process.call_count == 3
# Проверяем порядок и аргументы вызовов
expected_calls = [call(1), call(2), call(3)]
assert mock_worker.process.call_args_list == expected_calls |
|
При разработке тестов для API-клиентов часто требуется имитировать различные HTTP-ответы:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def test_api_client():
# Создаём последовательность разных ответов
mock_response_success = Mock(status_code=200)
mock_response_success.json.return_value = {"status": "success", "data": [1, 2, 3]}
mock_response_error = Mock(status_code=404)
mock_response_error.json.return_value = {"status": "error", "message": "Not found"}
# Настраиваем мок для последовательного возврата разных ответов
with patch("requests.get", side_effect=[mock_response_success, mock_response_error]):
# Первый запрос - успешный
result1 = app.api_client.fetch_resource("users")
assert result1["status"] == "success"
# Второй запрос - с ошибкой
result2 = app.api_client.fetch_resource("invalid")
assert result2["status"] == "error" |
|
Продвинутые стратегии
Освоив базовые техники мокинга, пора погрузиться в более сложные сценарии, с которыми сталкиваются разработчики при создании тестов для современных Python-приложений. Одним из наиболее востребованных направлений становится тестирование асинхронного кода, популярность которого растёт с каждым годом.
Чтобы успешно мокировать асинхронные функции, необходимо учитывать их особенности и использовать специальные подходы:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| import asyncio
from unittest.mock import patch, AsyncMock
async def fetch_data(url):
# Реальная функция, которая делает HTTP-запрос
# и возвращает данные
pass
async def process_user_data(user_id):
data = await fetch_data(f"/api/users/{user_id}")
return {"processed": True, "user_id": user_id, "data": data}
# Тестирование асинхронной функции
@patch("module.fetch_data", new_callable=AsyncMock)
async def test_process_user_data(mock_fetch):
# Настраиваем возвращаемое значение для асинхронной функции
mock_fetch.return_value = {"name": "Иван", "email": "ivan@example.com"}
result = await process_user_data(42)
# Проверяем, что мок был вызван с правильными параметрами
mock_fetch.assert_called_once_with("/api/users/42")
# Проверяем результат
assert result["processed"] == True
assert result["data"]["name"] == "Иван" |
|
В этом примере мы используем AsyncMock – специальный класс, добавленный в Python 3.8, который возвращает корутины вместо обычных значений. Для более ранних версий Python можно использовать обычный Mock с несколько более сложной настройкой:
| Python | 1
2
3
4
| # Для Python < 3.8
mock_fetch = Mock()
mock_fetch.return_value = asyncio.Future()
mock_fetch.return_value.set_result({"name": "Иван", "email": "ivan@example.com"}) |
|
При тестировании асинхронных контекстных менеджеров ситуация усложняется ещё больше. Необходимо имитировать не только вызов функций, но и правильное поведение методов __aenter__ и __aexit__:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| class MockAsyncContextManager:
async def __aenter__(self):
return {"connection": "established"}
async def __aexit__(self, exc_type, exc_val, exc_tb):
return None
# Использование в тесте
@patch("module.AsyncDatabaseConnection", return_value=MockAsyncContextManager())
async def test_db_operation(mock_db_conn):
# Код, использующий асинхронный контекстный менеджер
result = await perform_db_operation()
assert result["status"] == "success" |
|
Подделка внешних API – ещё одна область, где продвинутые техники мокинга оказываются особенно полезными. Разработчику часто требуется не просто заменить функцию на мок, а эмулировать сложное поведение:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| def test_weather_api_client():
# Создаём имитацию ответов API для разных запросов
responses = {
"London": {"temp": 15, "condition": "Cloudy"},
"Moscow": {"temp": 5, "condition": "Snow"},
"NotFound": None
}
# Функция-имитатор, которая вернёт разные данные в зависимости от города
def mock_get_weather(city):
if city not in responses:
raise ValueError(f"Unknown city: {city}")
if city == "NotFound":
return {"error": "City not found", "code": 404}
return {"status": "success", "data": responses[city]}
# Применяем нашу имитацию
with patch("weather_service.api.get_weather", side_effect=mock_get_weather):
# Тестируем успешный сценарий
london_weather = weather_service.get_temperature("London")
assert london_weather == 15
# Тестируем обработку ошибок
not_found = weather_service.get_temperature("NotFound")
assert not_found is None
# Тестируем обработку исключений
with pytest.raises(ValueError):
weather_service.get_temperature("InvalidCity") |
|
При работе с контекстными менеджерами библиотека unittest.mock предлагает специальный метод patch.object(), который позволяет мокировать только определённые методы или атрибуты объекта:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| from unittest.mock import patch
import contextlib
class DatabaseConnection:
def __enter__(self):
# Соединение с базой данных
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# Закрытие соединения
pass
def execute(self, query):
# Выполнение запроса к базе
pass
def test_db_operations():
# Создаём настоящий экземпляр
db_conn = DatabaseConnection()
# Мокируем только метод execute
with patch.object(db_conn, "execute", return_value=["row1", "row2"]):
with db_conn: # Вызываются реальные __enter__ и __exit__
result = db_conn.execute("SELECT * FROM users") # Но execute мокирован
assert len(result) == 2 |
|
Частичное мокирование – ценный инструмент, когда вы хотите сохранить большую часть реальной функциональности, но изменить поведение только отдельных компонентов. Это особенно удобно при тестировании объектов, которые сложно или невозможно полностью имитировать:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def test_partial_mocking():
# Создаём реальный объект для работы с файлами
file_manager = FileManager("/path/to/directory")
# Мокируем только метод удаления файлов, чтобы избежать реальных изменений
with patch.object(file_manager, "delete_file"):
file_manager.process_directory()
# Реальные файлы были обработаны, но не удалены
file_manager.delete_file.assert_called()
# Можем проверить, с какими параметрами вызывался метод
deleted_files = [call[0][0] for call in file_manager.delete_file.call_args_list]
assert "temp.txt" in deleted_files |
|
Проблема "слишком гибких" моков, рассмотренная ранее, становится особенно острой в больших проектах, где интерфейсы часто меняются. Механизмы spec и spec_set можно применять более изощрённо, чтобы гарантировать точное соответствие интерфейсов:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| from unittest.mock import create_autospec
# Класс с сложным интерфейсом
class PaymentProcessor:
def validate_card(self, card_number, expiry, cvv):
pass
def process_payment(self, amount, currency, card_data):
pass
def refund(self, transaction_id, amount=None):
pass
def test_payment_flow():
# Создаём строгий мок с полной спецификацией
mock_processor = create_autospec(PaymentProcessor, spec_set=True, instance=True)
# Настраиваем поведение
mock_processor.validate_card.return_value = True
mock_processor.process_payment.return_value = {"transaction_id": "tx123", "status": "approved"}
# Использование в тесте
payment_service = PaymentService(processor=mock_processor)
result = payment_service.charge_customer(100, "USD", "4111111111111111", "12/25", "123")
# Проверки
mock_processor.validate_card.assert_called_once()
mock_processor.process_payment.assert_called_once()
assert result["status"] == "success"
# Попытка вызвать несуществующий метод вызовет ошибку:
# mock_processor.invalid_method() # AttributeError! |
|
Работа с библиотеками для доступа к базам данных или сетевым ресурсам часто требует особых подходов к мокированию. Например, при тестировании кода, взаимодействующего с ORM-фреймворками вроде SQLAlchemy, приходится имитировать не только результаты запросов, но и саму сессию работы с базой:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| def test_complex_orm_query():
# Создаём цепочку имитаций для QueryBuilder
mock_session = Mock()
mock_query = mock_session.query.return_value
mock_filtered = mock_query.filter.return_value
mock_ordered = mock_filtered.order_by.return_value
# Настраиваем финальный результат запроса
expected_result = [Mock(id=1, name="Первый"), Mock(id=2, name="Второй")]
mock_ordered.all.return_value = expected_result
with patch("app.db.session", mock_session):
result = app.users.get_active_users()
# Проверяем, что цепочка методов вызывалась правильно
mock_session.query.assert_called_once()
mock_query.filter.assert_called_once()
mock_filtered.order_by.assert_called_once()
assert len(result) == 2 |
|
При мокировании временных функций часто возникают сложности, связанные с необходимостью контролировать "ход времени" в тестах. Для этого существуют специализированные техники:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| from unittest.mock import patch
from datetime import datetime, timedelta
def test_time_dependent_function():
# Фиксируем текущее время
fixed_now = datetime(2023, 1, 1, 12, 0, 0)
# Создаём последовательность дат для имитации течения времени
time_sequence = [
fixed_now,
fixed_now + timedelta(minutes=5),
fixed_now + timedelta(minutes=10)
]
# Патчим datetime.now, чтобы он возвращал наши значения последовательно
with patch('app.datetime') as mock_datetime:
mock_datetime.now.side_effect = time_sequence
# Код, использующий datetime.now(), получит наши фиксированные значения
result = app.process_with_timeout(10) # 10 минут таймаут
assert result.status == "completed"
# Можно также проверить количество проверок времени
assert mock_datetime.now.call_count == 3 |
|
В приложениях, использующих многопоточность или мультипроцессинг, мокирование становится особенно сложной задачей. Взаимодействие между потоками нужно контролировать, чтобы тесты выполнялись предсказуемо:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| def test_threaded_processing():
# Создаём мок для worker-функции
mock_worker = Mock()
# Имитируем поведение, которое учитывает многопоточность
def side_effect(item):
# Можно даже имитировать задержки
time.sleep(0.01)
return item * 2
mock_worker.side_effect = side_effect
with patch('app.worker_function', mock_worker):
# Запускаем код, который обрабатывает данные в нескольких потоках
results = app.parallel_process([1, 2, 3, 4, 5])
# После завершения всех потоков проверяем, что функция вызывалась правильно
assert mock_worker.call_count == 5
# Проверяем результаты, которые должны быть собраны из всех потоков
assert sorted(results) == [2, 4, 6, 8, 10] |
|
Тестирование кода, работающего с файловой системой, тоже требует особого внимания:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| from unittest.mock import patch, mock_open
def test_file_operations():
# Создаём имитацию файлового объекта
mock_file_data = """
key1=value1
key2=value2
"""
# Патчим встроенную функцию open
with patch('builtins.open', mock_open(read_data=mock_file_data)):
# Код, который читает и парсит файл
config = app.read_config('config.ini')
# Проверяем, что данные были прочитаны правильно
assert config['key1'] == 'value1'
assert config['key2'] == 'value2' |
|
При имитации сложных объектов часто бывает удобнее создать фабрику моков, чтобы не дублировать код настройки:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| def create_response_mock(status_code=200, json_data=None, headers=None):
"""Фабрика для создания моков HTTP-ответов"""
mock_response = Mock()
mock_response.status_code = status_code
mock_response.json.return_value = json_data or {}
mock_response.headers = headers or {'Content-Type': 'application/json'}
return mock_response
def test_api_client_retry():
# Создаём последовательность ответов с разными статусами
responses = [
create_response_mock(status_code=500), # Первый запрос - ошибка сервера
create_response_mock(status_code=200, json_data={"status": "success"}) # Успех после повтора
]
with patch('requests.get', side_effect=responses):
result = app.fetch_with_retry('https://api.example.com/data')
assert result["status"] == "success"
# Проверяем, что было два попытки запроса
assert requests.get.call_count == 2 |
|
Важной стратегией при написании тестов с моками является сохранение баланса между изоляцией и реалистичностью. Чрезмерное использование моков может привести к ситуации, когда тесты проходят, но реальное приложение не работает. В то же время, отсутствие мокирования для внешних зависимостей делает тесты нестабильными. Хороший подход – комбинировать разные стратегии тестирования, используя моки для изоляции внешних зависимостей, но сохраняя интеграционные тесты для проверки взаимодействия компонентов системы. Это позволяет достичь как скорости и надёжности модульных тестов, так и уверенности в правильной работе системы в целом.
Частые ловушки и их обход
Несмотря на гибкость библиотеки unittest.mock, работа с ней таит в себе множество потенциальных ловушек.
Одна из самых распространённых ошибок связана с неправильным указанием пути при использовании patch(). Вспомните правило: патчить нужно там, где объект используется, а не где он определён. Рассмотрим типичный сценарий проблемы:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # В файле api.py
def fetch_data():
# ...
# В файле service.py
from api import fetch_data
def process_data():
data = fetch_data()
# обработка данных
return data
# В файле test_service.py
from unittest.mock import patch
@patch('api.fetch_data') # НЕПРАВИЛЬНО!
def test_process_data(mock_fetch):
# ... |
|
Этот тест не будет работать, потому что функция fetch_data импортируется и используется в модуле service, а не напрямую из модуля api. Правильная версия выглядит так:
| Python | 1
2
3
| @patch('service.fetch_data') # ПРАВИЛЬНО!
def test_process_data(mock_fetch):
# ... |
|
Ещё одна частая проблема – опечатки в названиях атрибутов. Mock с готовностью создаёт любые атрибуты "на лету", что может маскировать ошибки в коде:
| Python | 1
2
3
4
5
6
| mock_response = Mock()
# Опечатка - "stauts" вместо "status"
mock_response.configure_mock(stauts_code=200)
# Этот тест пройдёт, но реальный код сломается!
assert mock_response.stauts_code == 200 |
|
Для предотвращения подобных ошибок используйте спецификации:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| # Создаём класс или протокол, описывающий ожидаемый интерфейс
class Response:
status_code: int
def json(self):
pass
# Теперь мок будет строго следовать интерфейсу
mock_response = Mock(spec=Response)
try:
mock_response.stauts_code = 200 # Вызовет AttributeError
except AttributeError:
print("Опечатка обнаружена!") |
|
Разработчики часто сталкиваются с проблемами при мокировании встроенных или импортированных функций. Например, попытка подменить встроенную функцию open может оказаться не такой простой, как кажется:
| Python | 1
2
3
4
5
6
7
| # Попытка напрямую заменить встроенную функцию
with patch('open'): # НЕ СРАБОТАЕТ!
# ...
# Правильный подход - патчить через модуль builtins
with patch('builtins.open'):
# ... |
|
При тестировании декораторов возникает специфическая проблема: функция уже обёрнута на момент импорта, и поэтому простой патч не помогает. Решение требует либо патчинга самого декоратора, либо использования более сложных техник:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Декоратор в utils.py
def require_auth(func):
def wrapper(*args, **kwargs):
# Проверка аутентификации
return func(*args, **kwargs)
return wrapper
# Функция в views.py
@require_auth
def get_user_data(user_id):
# ...
# В тестах - патчим сам декоратор
with patch('views.require_auth', lambda f: f):
# Теперь декорированная функция вызывается напрямую |
|
Важная ловушка связана с изолированностью тестов. Глобальное состояние патчей может привести к неожиданным взаимодействиям между тестами:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| # Опасный способ - глобальный патч
patcher = patch('module.function')
mock_function = patcher.start()
# Если забыть вызвать patcher.stop(), это повлияет на все последующие тесты!
def test_first():
mock_function.return_value = 'test1'
# ...
def test_second():
# mock_function всё ещё активен с настройками из предыдущего теста!
# ... |
|
Для надёжного управления патчами используйте декораторы, контекстные менеджеры или правильно настроенные setUp/tearDown методы.
Отладка тестов с моками может быть сложной из-за многоуровневости патчей. Полезный приём – временное отключение моков для локализации проблемы:
| Python | 1
2
3
4
5
| def test_problematic():
# Закомментируйте патчи по одному, чтобы найти проблемный
# with patch('module.func1') as mock1:
with patch('module.func2') as mock2:
# ... |
|
Для сложных объектных моделей и ORM вызовы методов часто выполняются по цепочке. Ошибки в настройке таких цепочек – распространённая проблема:
| Python | 1
2
3
4
5
6
7
8
| # Неполная настройка цепочки вызовов
mock_session = Mock()
mock_query = mock_session.query.return_value
# Забыли настроить результат filter()
[H2]mock_filtered = mock_query.filter.return_value[/H2]
# Это вызовет ошибку - next_method не определён
result = some_function_that_calls_query_filter_next_method() |
|
При работе с моками в многопоточных приложениях важно учитывать проблемы синхронизации. Тесты могут стать нестабильными из-за состояния гонки между потоками:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| def test_concurrent_calls():
mock_service = Mock()
# Запускаем параллельные вызовы
with ThreadPoolExecutor(max_workers=10) as pool:
futures = [pool.submit(call_service, mock_service) for _ in range(10)]
# Ждём завершения всех вызовов
wait(futures)
# Этот тест может быть нестабильным, так как порядок вызовов не гарантирован
assert mock_service.call_count == 10 |
|
Вместо проверки последовательности вызовов в многопоточных тестах лучше фокусироваться на общих результатах или использовать синхронизационные примитивы для обеспечения предсказуемости.
При использовании side_effect с генераторами или асинхронными функциями легко допустить ошибку, не учтя особенности их работы:
| Python | 1
2
3
4
5
| # Некорректная настройка генератора
mock_function.side_effect = (i for i in range(5))
# Правильный подход - использовать список
mock_function.side_effect = list(range(5)) |
|
Проблемы с моками могут возникать и при взаимодействии с кодом, который использует рефлексию или динамические атрибуты. Например, некоторые ORM и фреймворки часто используют метапрограммирование:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| # Библиотека, использующая динамические проверки типов
def validate_model(model):
# Проверяет, являются ли атрибуты экземплярами нужных типов
if not isinstance(model.id, int):
raise TypeError("id должен быть целым числом")
# ... другие проверки
# При тестировании
mock_model = Mock()
mock_model.id = "строка вместо числа" # Обычный мок пропустит ошибку типа!
validate_model(mock_model) # Тест пройдёт успешно, но реальный код сломается |
|
Для решения этой проблемы подходит либо создание класса-заглушки с правильными типами, либо использование spec с тщательной настройкой атрибутов:
| Python | 1
2
3
4
5
| class RealModel:
id: int = 0
mock_model = Mock(spec=RealModel)
mock_model.id = 42 # Правильный тип |
|
Отдельная категория проблем возникает при сериализации моков. Если код пытается сохранить мок-объект в JSON или pickle, это приведёт к ошибке:
| Python | 1
2
3
4
5
6
7
8
| import json
from unittest.mock import Mock
data = {"key": Mock()}
try:
serialized = json.dumps(data) # Вызовет исключение!
except TypeError as e:
print(f"Ошибка сериализации: {e}") |
|
Решение – создавать специальные заглушки для сериализации или использовать паттерн "тестового двойника":
| Python | 1
2
3
4
5
6
7
8
9
10
| class SerializableMock:
def __init__(self, return_value=None):
self.called = False
self.return_value = return_value
def __call__(self, *args, **kwargs):
self.called = True
self.args = args
self.kwargs = kwargs
return self.return_value |
|
При использовании инструментов для автоматизации создания моков (например, библиотек factory_boy или pytest-mock) возникают свои подводные камни:
| Python | 1
2
3
4
5
6
7
| # С pytest-mock
def test_with_mocker(mocker):
# Патчим метод, но забываем о настройке возвращаемого значения
mocker.patch('module.function') # return_value не задан!
# Код будет пытаться использовать результат function(), получит новый мок
# и, возможно, попытается вызвать его методы, что может привести к неожиданным результатам |
|
Такая ситуация может привести к "каскаду моков" – цепочке автоматически созданных моков, которые могут скрыть реальные проблемы в коде.
Тестирование статических методов и классов требует особого внимания:
| Python | 1
2
3
4
5
6
7
8
9
| class Utils:
@staticmethod
def format_date(date):
# ...
# Патчинг статического метода
with patch.object(Utils, 'format_date') as mock_format:
mock_format.return_value = "2023-01-01"
# ... |
|
Отдельного упоминания заслуживают ошибки при использовании autospec. Этот параметр может создать ложное чувство безопасности:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| # Класс с приватным методом
class Service:
def public_method(self):
return self._private_helper()
def _private_helper(self):
return "real result"
# Тест с autospec
with patch('module.Service', autospec=True) as MockService:
instance = MockService.return_value
# instance._private_helper НЕ будет автоматически создан,
# если public_method() вызывает его внутри себя! |
|
В таких случаях необходимо явно настроить все методы, которые могут вызываться внутри тестируемого кода, даже приватные.
При работе с микросервисной архитектурой моки часто используются для имитации API-вызовов. Распространённая ошибка – создание слишком "умных" моков:
| Python | 1
2
3
4
5
6
7
8
9
10
11
| # Слишком сложная имитация сервиса
mock_service = Mock()
def complex_logic(query):
# Реализуем бизнес-логику прямо в моке!
if 'error' in query:
return {'status': 'error'}
return {'status': 'success', 'data': [...]}
mock_service.search.side_effect = complex_logic
# Если настоящий сервис изменит логику, тесты могут продолжать проходить! |
|
Лучший подход – делать моки максимально простыми и проверять взаимодействие, а не дублировать логику.
И наконец, важно помнить об ограничениях моков. Они отлично подходят для модульного тестирования, но для проверки интеграции компонентов часто требуются другие стратегии:
| Python | 1
2
3
4
5
6
7
| # Вместо чрезмерного мокирования для интеграционных тестов
# лучше использовать легковесные заглушки или контейнеры:
@pytest.mark.integration
def test_database_integration():
# Используем тестовую БД в Docker вместо моков
with docker_test_database() as db:
# Реальное взаимодействие с базой |
|
Правильный баланс между мокированием и реальными компонентами – ключ к созданию надежных и поддерживаемых тестов. Моки – мощный инструмент, но требующий осознанного применения.
Проблемы с наследуемыми моками заслуживают отдельного внимания. При создании иерархий моков или при работе с классами, имеющими сложную структуру наследования, легко столкнуться с непредвиденным поведением:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| class Parent:
def method(self):
return "parent method"
class Child(Parent):
def child_specific(self):
return "child method"
# Неправильно - мок не будет иметь унаследованных методов
mock_child = Mock(spec=Child)
try:
result = mock_child.method() # AttributeError!
except AttributeError:
print("Мок не включил родительские методы")
# Правильно - используем spec_set с параметром instance=True
mock_child = create_autospec(Child, spec_set=True, instance=True)
mock_child.method() # Работает корректно |
|
Проблемы с синхронизацией при применении нескольких патчей одновременно также могут вызывать головную боль:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
| # Ошибка из-за неправильного порядка применения декораторов
@patch('module.object1')
@patch('module.object2')
def test_function(mock2, mock1): # Порядок аргументов ОБРАТНЫЙ порядку декораторов!
# ...
# Правильный порядок
@patch('module.object1')
@patch('module.object2')
def test_function(mock2, mock1):
# Аргументы идут в обратном порядке от декораторов
# mock2 соответствует object2
# mock1 соответствует object1 |
|
Ещё одна сложность возникает при тестировании кода, который настроен на внедрение зависимостей через аргументы функций:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| def process_data(data, processor=default_processor):
return processor(data)
# Неэффективный подход - избыточное мокирование
@patch('module.default_processor')
def test_process_data(mock_processor):
mock_processor.return_value = "processed"
result = process_data("data") # Используется мок
assert result == "processed"
# Лучший подход - напрямую передать мок как аргумент
def test_process_data_better():
mock_processor = Mock(return_value="processed")
result = process_data("data", processor=mock_processor)
assert result == "processed" |
|
При тестировании импортов с подменой модулей тоже возникают типичные проблемы:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
| # Модуль app.py импортирует requests
import requests
# Тест пытается патчить модуль напрямую
@patch('requests') # Не сработает!
def test_function(mock_requests):
# ...
# Правильное решение - патчить в контексте использования
@patch('app.requests')
def test_function(mock_requests):
# ... |
|
Работа с глобальными объектами или синглтонами в тестах может создать неожиданные конфликты:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # Глобальный объект конфигурации
config = {
'debug': False,
'api_url': 'https://example.com'
}
def get_api_url():
return config['api_url']
# Проблема: изменение глобального состояния может повлиять на другие тесты
def test_api_function():
# Меняем глобальную конфигурацию
original = config['api_url']
config['api_url'] = 'https://test.com'
try:
# Тестовый код
assert get_api_url() == 'https://test.com'
finally:
# Восстанавливаем конфигурацию
config['api_url'] = original
# Лучшее решение с использованием mock.patch.dict
def test_api_function_better():
with patch.dict('module.config', {'api_url': 'https://test.com'}):
assert get_api_url() == 'https://test.com'
# За пределами контекста конфигурация автоматически восстанавливается |
|
Некоторые фреймворки и библиотеки представляют особую сложность для мокирования. Например, Django ORM или SQLAlchemy используют сложные цепочки вызовов, которые трудно имитировать:
| Python | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # Сложный запрос с SQLAlchemy
users = (
session.query(User)
.filter(User.active == True)
.order_by(User.name)
.limit(10)
.all()
)
# Создание цепочки моков для SQLAlchemy может быть утомительным
mock_query = Mock()
mock_filter = Mock()
mock_order = Mock()
mock_limit = Mock()
mock_result = [Mock(id=1, name='User1'), Mock(id=2, name='User2')]
mock_query.filter.return_value = mock_filter
mock_filter.order_by.return_value = mock_order
mock_order.limit.return_value = mock_limit
mock_limit.all.return_value = mock_result
# Упрощение с помощью вспомогательной функции
def create_query_mock(result):
"""Создает мок для цепочки запросов SQLAlchemy"""
mock = Mock()
chain = mock
for method in ['filter', 'order_by', 'limit']:
next_mock = Mock()
chain.__getattr__(method).return_value = next_mock
chain = next_mock
chain.all.return_value = result
return mock |
|
Искусство баланса в тестировании с моками
Фундаментальный вопрос, с которым сталкивается каждый разработчик — поиск оптимального баланса между модульными (юнит) и интеграционными тестами. Юнит-тесты с обильным использованием моков работают быстро, изолированно и позволяют проверить мельчайшие детали реализации. Но они создают иллюзорную безопасность — код может отлично взаимодействовать с моками, но сломаться при встрече с реальными объектами.
Интеграционные тесты, с другой стороны, проверяют, как компоненты взаимодействуют между собой в реальных условиях. Они медленнее, сложнее, но ближе к действительности. Опытные разработчики обычно придерживаются «тестовой пирамиды»: много быстрых юнит-тестов с моками в основании, затем слой интеграционных тестов для критических компонентов, и наконец небольшое количество медленных системных тестов на вершине.
В Python существуют альтернативные подходы к мокингу. Библиотека pytest-mock предлагает более лаконичный API для создания моков, идеально интегрируясь с фреймворком pytest:
| Python | 1
2
3
4
| def test_function(mocker):
# Более компактная запись по сравнению с unittest.mock
mock_service = mocker.patch('module.Service')
assert mock_service.called |
|
Для тестирования HTTP-запросов многие предпочитают специализированную библиотеку responses, которая точнее имитирует поведение HTTP-клиентов:
| Python | 1
2
3
4
5
6
7
| import responses
@responses.activate
def test_api_call():
responses.add(responses.GET, 'https://api.example.com/data',
json={'key': 'value'}, status=200)
# Код, делающий запрос, получит предопределённый ответ |
|
Библиотека freezegun незаменима для контроля над временем в тестах, позволяя «заморозить» его в определённой точке:
| Python | 1
2
3
4
5
| from freezegun import freeze_time
@freeze_time("2023-01-01")
def test_time_dependent():
# Все вызовы datetime.now() вернут 2023-01-01 |
|
Для любителей более строгой типизации существует mypy-mock, добавляющий поддержку типов для моков и делающий код тестов более надёжным.
В практике тестирования постепенно набирает популярность подход к мокированию на уровне архитектуры — разработка с учётом возможности тестирования. Код, спроектированный с применением принципов инверсии зависимостей и инъекции зависимостей, гораздо проще тестировать, часто вообще без патчинга.
mock with relative path Народ, не сталкивались ли с проблемой, как в питоне, юниттестах, мокать то, что импортировано по относительному пути?
Очень не хочется делать там... Python.csv.library создать функции удаления и редактирования данных о книге, хранящихся в csv консольный интерфейс предполагает нажатие 1-5 клавиш со следующим функционалом:
1 - выводит список книг
2 - добавляет данные о новой книге
3 -... ValueError: Timeout value connect was <object object at 0x0000024067B647F0>, but it must be an int, float or None Всем доброго времени суток. Столкнулся с проблемами относительно selenium. Изначально пробовал запускать под MS Edge, но судя по всему такое... TypeError: 'str' object is not callable. Как исправить? - Python import time, socket, threading, requests, urllib, socks
def thread(numthreads, attack):
threads =
for n in range(numthreads):
... Ошибка Python TypeError: 'Matrix' object is not subscriptable Не могу понять в чем ошибка.
line 51, in __str__ self.matrix = 0 TypeError: 'Matrix' object is not subscriptable
Мне подсказали что не... 'NoneType' object is not subscriptable Python. Что делать? Написал программу, которая по входящим правилам передвижениям (север, юг, запад, восток) для перекрёстков, находит самый короткий путь из точки А = ... Где прочитать про реализацию класcа object на Python Уважаемые :scratch:, где прочитать про реализацию класcа object на Python. Класс это тип, а тип это класс, это все понятно.
Структуры данных и... Python PyQt5 ошибка AttributeError: 'builtin_function_or_method' object has no attribute 'objectName' File "D:\Alexander\Projects\Python\Lyceum\Gartic_Phone\canvas\canvasClass.py", line 34, in mousePressEvent
if... Python & REGEX: TypeError: 'NoneType' object is not subscriptable TypeError: 'NoneType' object is not subscriptable
Привет. У меня есть эта ошибка, но в строке с формулой регулярного выражения. Как я могу это... Python | Ошибка "TypeError: 'NoneType' object is not iterable" Добрый день всем! Реализую алгоритм линейного поиска на Python с замером времени, симулируя Best-Case-сценарий (когда искомый элемент в начале... Python TypeError: 'int' object is not callable На парах решали задачи на Python и делали самый простой калькулятор. По приходу домой решил поиграться с ним и сделать получше, но visual studio... pyqt - TypeError: unable to convert a Python 'int' object to a C++ 'QString' instance Всем привет. Мне нужно инкрементировать переменную и передавать ее в GUI на QML.
Код на python:
import sys
from PyQt5.QtCore import QUrl ...
|