Практический пример использования классов-заглушек boto3

Я Python-разработчик, использую Django (с rest framework) и сервисы AWS. Конечно же, я использую boto3 для соединения с некоторыми сервисами AWS из приложения.

Кстати, когда вы добавляете некоторые тесты для использования кода boto3, вы должны использовать mock, потому что нельзя подключать реальные ресурсы при выполнении юнит-тестов. Сначала я использовал unittest.mock, но нашел класс stubber, описанный в официальном документе.

Stubber Reference — документация botocore 1.26.7

Но я не нашел практического примера кода, поэтому пишу этот пост сейчас.

Обзор

Прежде всего, следующий код — это то, как использовать класс stubber (более подробно я расскажу в следующих разделах).

>>> import boto3
>>> from botocore.stub import Stubber
>>> client = boto3.client('cognito-idp')
>>> stubber = Stubber(client)
>>> stubber.add_response('list_users', {'Users': []})
>>> stubber.add_client_error('admin_get_user', service_error_code='UserNotFoundException')
>>> stubber.activate()
>>> client.list_users(UserPoolId='dummpy_id')
{'Users': []}
>>> client.admin_get_user(UserPoolId='dummpy_id', Username='user@example.com')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/hikaru/.local/lib/python3.8/site-packages/botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/home/hikaru/.local/lib/python3.8/site-packages/botocore/client.py", line 676, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.UserNotFoundException: An error occurred (UserNotFoundException) when calling the AdminGetUser operation:
Вход в полноэкранный режим Выход из полноэкранного режима

Инициализация

Вы можете сконструировать клиента как обычно. В моем примере, указав cognito-idp, вы создадите клиента, который сможет работать с пользователями AWS Cognito, группами и так далее.

>>> client = boto3.client('cognito-idp')
>>> stubber = Stubber(client)
Вход в полноэкранный режим Выход из полноэкранного режима

Паттерн передачи

Метод add_response может имитировать метод клиента boto3. add_response принимает два аргумента, первый — имя целевого метода, второй — возвращаемое значение. Например, у клиента cognito-idp есть метод list_users. Следующий код исправит client.list_users(some_args), чтобы вернуть {'Users': []}.

>>> stubber.add_response('list_users', {'Users': []})
Вход в полноэкранный режим Выход из полноэкранного режима

Ошибка

Вы можете подражать, чтобы вызвать ошибку. В классе Stubber есть метод add_client_error. Он также получает 2 аргумента, первый — имя метода, второй — код ошибки.

Следующий код является примером исправления admin_get_user, чтобы вызвать UserNotFoundException.

>>> stubber.add_client_error('admin_get_user', service_error_code='UserNotFoundException')
Вход в полноэкранный режим Выход из полноэкранного режима

Активация

Вызовите метод activate() или создайте блок with.

stubber.activate()
client.list_users()

# or

with stubber:
    client.list_users()
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы хотите узнать больше подробностей, прочитайте официальную справку.

Пример кода модульного тестирования

Как вы можете видеть, вы можете гибко издеваться. Но вы можете сказать, что не знаете, как это использовать.

Поэтому на более практическом примере я расскажу, как использовать класс stubber.

Пример функции

Я подготовил простой пример для тестирования. Функция get_user получает email и возвращает атрибуты пользователя, у которого такой же адрес электронной почты.

# main.py
import os

import boto3
from botocore.exceptions import ClientError

os.environ['AWS_ACCESS_KEY_ID'] = 'DUMMY_VALUE'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'DUMMY_VALUE'
os.environ['AWS_DEFAULT_REGION'] = 'ap-northeast-1'

def get_user(email: str) -> dict:
    client = boto3.client('cognito-idp')

    try:
        user = client.admin_get_user(
            UserPoolId='DUMMY_USER_POOL_ID',
            Username=email,
        )
    except ClientError as error:
        if error.response['Error']['Code'] == 'UserNotFoundException':
            return None
        raise

    return user['UserAttributes']
Вход в полноэкранный режим Выход из полноэкранного режима

Стратегия тестирования

Вероятно, вы захотите написать как минимум 2 тестовых случая.

  1. Эта функция возвращает корректные атрибуты пользователя.
  2. Эта функция возвращает None, когда admin_get_user вызывает ClientError с кодом UserNotFoundException.

Итак, я буду использовать класс Stubber для написания двух тестов.

Первый случай

В этом примере я использую unittest.mock из стандартной библиотеки Python.

# tests/test_main.py
import unittest
from unittest import mock
import boto3
from botocore.stub import Stubber

from main import get_user

class TestGetUser(unittest.TestCase):

    def test_get_user(self):
        client = boto3.client('cognito-idp')
        stubber = Stubber(client)
        stubber.add_response('admin_get_user', {
            'Username': 'user',
            'UserAttributes':
                [
                    {'Name': 'sub', 'Value': 'aa45403e-8ba5-42ab-ab27-78a6e9335b23'},
                    {'Name': 'email', 'Value': 'user@example.com'}
                ]
        })
        stubber.activate()
        with mock.patch('boto3.client', mock.MagicMock(return_value=client)):
            user = get_user('user')

        self.assertEqual(user,
                [
                    {'Name': 'sub', 'Value': 'aa45403e-8ba5-42ab-ab27-78a6e9335b23'},
                    {'Name': 'email', 'Value': 'user@example.com'}
                ]
        )
Вход в полноэкранный режим Выйти из полноэкранного режима

Примечательным моментом является с mock.patch('boto3.client', mock.MagicMock(return_value=client)):.

В этом случае необходимо не только активировать stubber, но и заменить boto3.client на stubber, так как boto3.client, построенный в тестовом методе, не используется в функции get_user. Блок with делает такую вещь.

Все остальное — обычные тестовые коды.

Второй случай

Второй случай практически аналогичен первому. В приведенном ниже коде опущены оператор импорта и определение класса TestCase.

def test_not_found_user(self):
    client = boto3.client('cognito-idp')
    stubber = Stubber(client)
    stubber.add_client_error('admin_get_user', 'UserNotFoundException')
    stubber.activate()
    with mock.patch('boto3.client', mock.MagicMock(return_value=client)):
        user = get_user('user')

    self.assertIsNone(user)
Вход в полноэкранный режим Выход из полноэкранного режима

Используя add_client_error, вы можете заставить возникнуть UserNotFoundError. Так, если в get_user возникает ClientError, эта функция возвращает None, как вы и ожидали.

Дополнительное содержание

Весь код этого примера находится здесь.

Заключение

Я написал практический пример юнит-тестов с boto3.

Метод boto3 возвращает большой словарь, поэтому вам может показаться сложным обработать ответ. Но, на самом деле, все, что вам нужно сделать, это утверждать только те данные, которые связаны со значением, используемым в приложении.

Хорошие модульные тесты облегчают поиск ошибок.

Давайте использовать mock и писать больше модульных тестов.

Спасибо, что прочитали эту статью!

Моя оригинальная статья написана на японском языке.

Оцените статью
Procodings.ru
Добавить комментарий