Я 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 тестовых случая.
- Эта функция возвращает корректные атрибуты пользователя.
- Эта функция возвращает
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 и писать больше модульных тестов.
Спасибо, что прочитали эту статью!
Моя оригинальная статья написана на японском языке.