Это продолжение моей предыдущей статьи о Pytest, которую вы можете найти ниже.



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

Насмешка

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

В pytest вы можете использовать плагин pytest-mock для имитации объектов в ваших тестах. Вот пример того, как вы можете использовать плагин pytest-mock для имитации HTTP-запроса:

import pytest
import requests
from unittest.mock import Mock

@pytest.fixture
def mock_get(mocker):
    mock = Mock()
    mocker.patch('requests.get', return_value=mock)
    return mock
def test_get_request(mock_get):
    mock_get.return_value.status_code = 200
    mock_get.return_value.json.return_value = {'key': 'value'}
    response = requests.get('http://example.com')
    assert response.status_code == 200
    assert response.json() == {'key': 'value'}

В этом примере фикстура mock_get используется для имитации функции requests.get. Прибор возвращает фиктивный объект, который вставляется в функцию requests.get с помощью метода mocker.patch.

В функции test_get_request фиктивный объект настроен на возврат определенного кода состояния и ответа JSON при вызове функции requests.get. Затем тестовая функция отправляет HTTP-запрос с помощью функции requests.get и проверяет, возвращены ли правильный код состояния и ответ JSON.

Вот еще несколько примеров разных типов моков, которые вы можете использовать в pytest с плагином pytest-mock:

Насмешливые свойства

Вы можете использовать атрибут side_effect фиктивного объекта для имитации свойства. Например:

import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_obj(mocker):
    mock = Mock()
    mock.some_property.side_effect = ['a', 'b', 'c']
    return mock
def test_mock_property(mock_obj):
    assert mock_obj.some_property == 'a'
    assert mock_obj.some_property == 'b'
    assert mock_obj.some_property == 'c'

В этом примере фикстура mock_obj возвращает фиктивный объект с атрибутом some_property, у которого атрибут side_effect установлен в список значений. Функция тестирования проверяет, возвращает ли атрибут some_property правильные значения при обращении к нему.

Насмешливые методы

Вы можете использовать атрибут return_value фиктивного объекта для имитации метода. Например:

Вот завершенный пример использования атрибута return_value для имитации метода в pytest:

import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_obj(mocker):
    mock = Mock()
    mock.some_method.return_value = 'mocked value'
    return mock
def test_mock_method(mock_obj):
    assert mock_obj.some_method() == 'mocked value'

В этом примере фикстура mock_obj возвращает фиктивный объект с методом some_method, для атрибута return_value которого задано определенное значение. Тестовая функция проверяет, возвращает ли метод some_method правильное значение при вызове.

Имитация менеджеров контекста

Вы можете использовать атрибуты __enter__ и __exit__ фиктивного объекта для имитации менеджера контекста. Например:

import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_obj(mocker):
    mock = Mock()
    mock.__enter__.return_value = 'mocked value'
    return mock
def test_mock_context_manager(mock_obj):
    with mock_obj as value:
        assert value == 'mocked value'

В этом примере фикстура mock_obj возвращает фиктивный объект с атрибутами __enter__ и __exit__. Атрибут __enter__ имеет атрибут return_value, установленный на определенное значение, а атрибут __exit__ не имеет никакого конкретного значения.

Тестовая функция использует оператор with для входа в фиктивный диспетчер контекста и проверяет, что значение, возвращаемое методом __enter__, является ожидаемым значением.

Чтобы имитировать AWS S3 в pytest, вы можете использовать библиотеку boto3 и плагин pytest-mock.

Вот пример того, как вы можете использовать эти инструменты для имитации корзины S3 в pytest:

import boto3
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_s3(mocker):
    mock = Mock(spec=boto3.client('s3'))
    mocker.patch('boto3.client', return_value=mock)
    return mock
def test_s3_upload(mock_s3):
    mock_s3.upload_file.return_value = None
    # Test code that uploads a file to S3
    # ...
    mock_s3.upload_file.assert_called_once()

В этом примере фикстура mock_s3 возвращает фиктивный объект, который вставляется в функцию boto3.client с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект boto3.client('s3'), поэтому его можно использовать для имитации операций S3 в ваших тестах.

Тестовая функция использует метод upload_file фиктивного клиента S3 для имитации загрузки файла S3 и проверяет, что метод upload_file вызывается один раз.

Чтобы имитировать наличие файла в AWS S3 в pytest, вы можете использовать библиотеку boto3 и плагин pytest-mock.

Вот пример того, как вы можете использовать эти инструменты, чтобы имитировать наличие файла в корзине S3 в pytest:

import boto3
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_s3(mocker):
    mock = Mock(spec=boto3.client('s3'))
    mocker.patch('boto3.client', return_value=mock)
    return mock
def test_s3_file_exists(mock_s3):
    mock_s3.head_object.return_value = {'ResponseMetadata': {'HTTPStatusCode': 200}}
    # Test code that checks if a file exists in S3
    # ...
    mock_s3.head_object.assert_called_once()

В этом примере фикстура mock_s3 возвращает фиктивный объект, который вставляется в функцию boto3.client с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект boto3.client('s3'), поэтому его можно использовать для имитации операций S3 в ваших тестах.

Тестовая функция использует метод head_object фиктивного клиента S3, чтобы имитировать наличие файла в корзине S3, и проверяет, что метод head_object вызывается один раз.

Чтобы имитировать другие ресурсы AWS в pytest, вы можете использовать библиотеку boto3 и плагин pytest-mock.

Процесс имитации других ресурсов AWS аналогичен процессу имитации S3. Вам нужно будет создать фиктивный объект с теми же методами и атрибутами, что и у реального ресурса, а затем использовать метод mocker.patch для исправления фиктивного объекта в библиотеке boto3.

Вот пример того, как вы можете использовать эти инструменты для имитации ресурса AWS DynamoDB в pytest:

import boto3
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_dynamodb(mocker):
    mock = Mock(spec=boto3.client('dynamodb'))
    mocker.patch('boto3.client', return_value=mock)
    return mock
def test_dynamodb_put_item(mock_dynamodb):
    mock_dynamodb.put_item.return_value = {'ResponseMetadata': {'HTTPStatusCode': 200}}
    # Test code that puts an item into a DynamoDB table
    # ...
    mock_dynamodb.put_item.assert_called_once()

В этом примере фикстура mock_dynamodb возвращает фиктивный объект, который вставляется в функцию boto3.client с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект boto3.client('dynamodb'), поэтому его можно использовать для имитации операций DynamoDB в ваших тестах.

Функция тестирования использует метод put_item фиктивного клиента DynamoDB для имитации вставки элемента в таблицу DynamoDB и проверяет, что метод put_item вызывается один раз.

Чтобы имитировать создание файла tar и проверить хэш существующего файла tar в pytest, вы можете использовать библиотеку tarfile и библиотеку hashlib.

Вот пример того, как вы можете использовать эти библиотеки для имитации создания tar-файла и проверки хэша существующего tar-файла в pytest:

import tarfile
import hashlib
import pytest

def test_create_tar_file():
    # Create a mock tar file in memory
    with tarfile.TarFile('', mode='w') as tar:
        tar.add('some/file/path.txt', arcname='file.txt')
    # Calculate the hash of the mock tar file
    tar_hash = hashlib.sha256()
    tar_hash.update(tar.getvalue())
    tar_hash = tar_hash.hexdigest()
    # Verify the hash of the mock tar file
    assert tar_hash == 'expected_tar_file_hash'

В этом примере класс tarfile.TarFile используется для создания фиктивного файла tar в памяти. Метод add используется для добавления файла в tar-файл, а аргумент mode устанавливается равным 'w' для записи в tar-файл.

Хэш фиктивного файла tar затем вычисляется с использованием функции hashlib.sha256 и методов update и hexdigest. Затем рассчитанный хэш сверяется с ожидаемым хэшем tar-файла.

Чтобы имитировать подключение Snowflake и чтение таблицы в pytest, вы можете использовать библиотеку snowflake-connector-python и плагин pytest-mock.

Вот пример того, как вы можете использовать эти инструменты для имитации соединения Snowflake и чтения таблицы в pytest:

import snowflake.connector
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_snowflake(mocker):
    mock_conn = Mock(spec=snowflake.connector.connect)
    mocker.patch('snowflake.connector.connect', return_value=mock_conn)
    return mock_conn
def test_read_snowflake_table(mock_snowflake):
    mock_cursor = Mock()
    mock_cursor.execute.return_value = [['column1', 'column2'], ['row1_col1', 'row1_col2'], ['row2_col1', 'row2_col2']]
    mock_snowflake.cursor.return_value = mock_cursor
    # Test code that reads a Snowflake table
    # ...
    mock_cursor.execute.assert_called_once()

В этом примере фикстура mock_snowflake возвращает фиктивный объект, который вставляется в функцию snowflake.connector.connect с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящее соединение Snowflake, поэтому его можно использовать для имитации операций Snowflake в ваших тестах.

Тестовая функция использует метод cursor фиктивного соединения Snowflake для создания фиктивного курсора и устанавливает метод execute фиктивного курсора для возврата списка строк из таблицы Snowflake. Затем тестовая функция проверяет, что метод execute фиктивного курсора вызывается один раз.

Чтобы имитировать ресурс AWS в pytest, вы можете использовать библиотеку boto3 и плагин pytest-mock.

Процесс имитации ресурса AWS аналогичен процессу имитации S3. Вам нужно будет создать фиктивный объект с теми же методами и атрибутами, что и у реального ресурса, а затем использовать метод mocker.patch для исправления фиктивного объекта в библиотеке boto3.

Вот пример того, как вы можете использовать эти инструменты для имитации ресурса AWS EC2 в pytest:

import boto3
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_ec2(mocker):
    mock = Mock(spec=boto3.client('ec2'))
    mocker.patch('boto3.client', return_value=mock)
    return mock
def test_ec2_run_instances(mock_ec2):
    mock_ec2.run_instances.return_value = {'ResponseMetadata': {'HTTPStatusCode': 200}}
    # Test code that runs EC2 instances
    # ...
    mock_ec2.run_instances.assert_called_once()

В этом примере фикстура mock_ec2 возвращает фиктивный объект, который вставляется в функцию boto3.client с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект boto3.client('ec2'), поэтому его можно использовать для имитации операций EC2 в ваших тестах.

Тестовая функция использует метод run_instances фиктивного клиента EC2 для имитации работы экземпляров EC2 и проверяет, что метод run_instances вызывается один раз.

Чтобы имитировать создание файла model.tar.gz и его существование в локальной папке и AWS S3 в pytest, вы можете использовать библиотеку tarfile и библиотеку boto3 вместе с плагином pytest-mock.

Вот завершенный пример использования библиотеки tarfile, библиотеки boto3 и плагина pytest-mock для имитации создания файла model.tar.gz и его существования в локальной папке и AWS S3 в pytest:

import tarfile
import boto3
import os
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_s3(mocker):
    mock = Mock(spec=boto3.client('s3'))
    mocker.patch('boto3.client', return_value=mock)
    return mock
def test_create_and_upload_model(mock_s3):
    # Create a mock model file in memory
    model_data = b'fake model data'
    model_file = io.BytesIO(model_data)
    # Create a mock tar file in memory
    with tarfile.TarFile('', mode='w') as tar:
        tar.addfile(tarfile.TarInfo(name='model.txt'), model_file)
    # Save the mock tar file to a local folder
    with open('local/folder/model.tar.gz', 'wb') as f:
        f.write(tar.getvalue())
    # Verify that the mock tar file exists in the local folder
    assert os.path.exists('local/folder/model.tar.gz')
    # Upload the mock tar file to an S3 bucket
    mock_s3.upload_file.return_value = {'ResponseMetadata': {'HTTPStatusCode': 200}}
    mock_s3.upload_file('local/folder/model.tar.gz', 'my-s3-bucket', 'model.tar.gz')
    # Verify that the mock tar file exists in the S3 bucket
    mock_s3.head_object.return_value = {'ResponseMetadata': {'HTTPStatusCode': 200}}
    assert mock_s3.head_object('my-s3-bucket', 'model.tar.gz')['ResponseMetadata']['HTTPStatusCode'] == 200

В этом примере фикстура mock_s3 возвращает фиктивный объект, который вставляется в функцию boto3.client с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект boto3.client('s3'), поэтому его можно использовать для имитации операций S3 в ваших тестах.

Сначала тестовая функция создает в памяти файл фиктивной модели, используя класс io.BytesIO и некоторые данные фиктивной модели. Затем он использует класс tarfile.TarFile для создания фиктивного файла tar в памяти и добавления файла фиктивной модели в tar-файл с помощью метода addfile.

Затем тестовая функция сохраняет фиктивный tar-файл в локальную папку, используя функцию open и метод write. Затем он проверяет наличие фиктивного tar-файла в локальной папке с помощью функции os.path.exists.

Наконец, тестовая функция использует метод upload_file фиктивного клиента S3 для загрузки фиктивного tar-файла в корзину S3. Затем он использует метод head_object фиктивного клиента S3, чтобы убедиться, что фиктивный файл tar существует в корзине S3.

Вот пример того, как вы можете использовать насмешки в проекте по науке о данных с помощью pytest:

import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_model(mocker):
    mock = Mock()
    mocker.patch('my_module.build_model', return_value=mock)
    return mock
def test_train_model(mock_model):
    # Test code that trains a model
    # ...
    mock_model.fit.assert_called_once()

В этом примере фикстура mock_model возвращает фиктивный объект, который вставляется в функцию my_module.build_model с помощью метода mocker.patch. Макет объекта имеет те же методы и атрибуты, что и реальный объект модели, поэтому его можно использовать для имитации обучения модели в ваших тестах.

Затем тестовая функция использует метод fit фиктивной модели, чтобы убедиться, что модель обучена правильно.

Вот еще один пример использования mocking в проекте по науке о данных с pytest:

import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_data(mocker):
    mock = Mock()
    mocker.patch('my_module.load_data', return_value=mock)
    return mock
def test_preprocess_data(mock_data):
    # Test code that preprocesses data
    # ...
    mock_data.reset_index.assert_called_once()
    mock_data.drop.assert_called_once()
    mock_data.fillna.assert_called_once()
def test_split_data(mock_data):
    # Test code that splits data into train and test sets
    # ...
    mock_data.sample.assert_called_once()
    mock_data.reset_index.assert_called_once()

В этом примере фикстура mock_data возвращает фиктивный объект, который вставляется в функцию my_module.load_data с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект данных, поэтому его можно использовать для имитации операций с данными в ваших тестах.

Функция test_preprocess_data использует методы reset_index, drop и fillna фиктивного объекта данных для проверки правильности предварительной обработки данных. Функция test_split_data использует методы sample и reset_index фиктивного объекта данных для проверки правильности разделения данных на обучающие и тестовые наборы.

Вот пример того, как вы можете использовать mocking в проекте API с pytest:

import pytest
import requests
from unittest.mock import Mock

@pytest.fixture
def mock_response(mocker):
    mock = Mock()
    mocker.patch('requests.get', return_value=mock)
    return mock
def test_get_data(mock_response):
    # Test code that makes a GET request to an API
    # ...
    mock_response.json.assert_called_once()
def test_post_data(mock_response):
    # Test code that makes a POST request to an API
    # ...
    mock_response.json.assert_called_once()
    mock_response.raise_for_status.assert_called_once()

В этом примере фикстура mock_response возвращает фиктивный объект, который вставляется в функцию requests.get с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект requests.Response, поэтому его можно использовать для имитации запросов API в ваших тестах.

Функция test_get_data использует метод json фиктивного объекта ответа для проверки правильности возврата данных из API. Функция test_post_data использует методы json и raise_for_status фиктивного объекта ответа для проверки правильности отправки данных в API и правильной обработки статуса ответа.

Чтобы имитировать запрос POST к API в pytest, вы можете использовать библиотеку requests и библиотеку unittest.mock.

Вот пример того, как вы можете имитировать POST-запрос к API в pytest:

import pytest
import requests
from unittest.mock import Mock

@pytest.fixture
def mock_response(mocker):
    mock = Mock()
    mocker.patch('requests.post', return_value=mock)
    return mock
def test_post_data(mock_response):
    # Test code that makes a POST request to an API
    # ...
    mock_response.json.assert_called_once()
    mock_response.raise_for_status.assert_called_once()

В этом примере фикстура mock_response возвращает фиктивный объект, который вставляется в функцию requests.post с помощью метода mocker.patch. Мок-объект имеет те же методы и атрибуты, что и настоящий объект requests.Response, поэтому его можно использовать для имитации запроса POST в ваших тестах.

Функция тестирования использует методы json и raise_for_status фиктивного объекта ответа, чтобы убедиться, что запрос POST выполнен правильно и что состояние ответа обрабатывается правильно.

Заключение

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

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

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

Спасибо за прочтение.