Различие моков и фикстур в тестировании на pytest

Опубликовано 2023.03.02

Моки и фикстуры используются при тестировании программного обеспечения, чтобы гарантировать надежность и правильность кода, но они служат разным целям.

Моки используются для замены зависимости тестируемого кода тестируемым объектом, который ведет себя определенным образом. Например, если функция зависит от подключения к базе данных, фиктивный объект можно использовать для имитации подключения к базе данных без фактического подключения к ней. Это позволяет тестировать функцию изолированно и без необходимости подключения к реальной базе данных.

Фикстуры, с другой стороны, используются для настройки начального состояния тестируемой системы. Фикстуры предоставляют известный набор данных или ресурсов, которые тест может использовать для обеспечения правильного поведения системы в определенных условиях. Например, тест, который проверяет авторизацию, может использовать фикстуру для создания набора тестовых учетных записей пользователей с известными именами пользователей и паролями.

Таким образом, макеты используются для замены зависимостей тестируемыми объектами, а фикстуры используются для предоставления исходных данных и ресурсов для тестирования. Оба являются важными инструментами для эффективного тестирования программного обеспечения.

Пример использования моков в pytest:

Допустим, у вас есть функция get_weather_data(), которая извлекает данные о погоде из внешнего API. Ваш код может выглядеть примерно так:

  1. import requests
  2.  
  3. def get_weather_data(city):
  4. url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid=your_api_key"
  5. response = requests.get(url)
  6. return response.json()

Чтобы протестировать эту функцию, вам нужно будет вызвать внешний API, который может быть медленным и ненадежным. Вместо этого вы можете использовать фиктивный объект для имитации ответа API и изолированного тестирования функции.

Чтобы использовать фиктивный объект в Pytest, вы можете использовать библиотеку unittest.mock, входящую в состав Python. Вот пример теста с использованием фиктивного объекта:

  1. from unittest.mock import MagicMock
  2. from my_module import get_weather_data
  3.  
  4. def test_get_weather_data():
  5. # Create a mock response object
  6. mock_response = MagicMock()
  7. mock_response.json.return_value = {"temperature": "20°C", "humidity": "60%"}
  8.  
  9. # Monkeypatch the requests library to return the mock response
  10. monkeypatch.setattr("requests.get", lambda url: mock_response)
  11.  
  12. # Call the function with the mock response
  13. result = get_weather_data("San Francisco")
  14.  
  15. # Assert that the function returned the expected data
  16. assert result["temperature"] == "20°C"
  17. assert result["humidity"] == "60%"

В этом примере мы создаем фиктивный объект ответа в формате JSON, содержащей данные о температуре и влажности. Затем мы используем monkeypatch.setattr(), чтобы заменить функцию request.get() лямбда-функцией, которая возвращает фиктивный ответ. Наконец, мы вызываем get_weather_data() с фиктивным ответом и ожидаем, что функция вернула ожидаемые данные.

Используя фиктивный объект, мы можем протестировать функцию get_weather_data() изолированно, не полагаясь на внешний API, что делает наши тесты быстрее, надежнее и проще в отладке.

Пример использования фикстур в pytest:

Допустим, у вас есть функция calculate_average(), которая вычисляет среднее значение списка чисел. Ваш код может выглядеть примерно так:

  1. def calculate_average(numbers):
  2. total = sum(numbers)
  3. count = len(numbers)
  4. if count == 0:
  5. return 0
  6. else:
  7. return total / count

Чтобы протестировать эту функцию, вам нужно будет создать тестовые данные для передачи в функцию. Вместо того, чтобы дублировать этот код в каждом тесте, вы можете использовать фикстуру для предоставления известного набора данных для каждого теста.

Чтобы использовать фикстуру в Pytest, вы можете использовать декоратор @pytest.fixture. Вот пример теста с использованием фикстуры:

  1. import pytest
  2. from my_module import calculate_average
  3.  
  4. @pytest.fixture
  5. def test_data():
  6. return [1, 2, 3, 4, 5]
  7.  
  8. def test_calculate_average(test_data):
  9. result = calculate_average(test_data)
  10. assert result == 3.0
  11.  
  12. def test_calculate_average_empty():
  13. result = calculate_average([])
  14. assert result == 0

В этом примере мы определяем фикстуру с именем test_data, которая возвращает список чисел для использования в наших тестах. Затем мы передаем эту фикстуру в качестве аргумента нашей тестовой функции test_calculate_average(). Когда Pytest запускает этот тест, он автоматически вызывает фикстуру test_data и передает возвращенные данные тестовой функции.

Используя фикстуру, мы можем предоставить известный набор данных для наших тестов и избежать дублирования кода в нескольких тестах. Это упрощает чтение, написание и поддержку тестов.