Ich versuche, eine Singluar-Methode aus dem boto3 s3-Clientobjekt zu verspotten, um eine Ausnahme auszulösen. Aber ich brauche alle anderen Methoden, damit diese Klasse normal funktioniert.
Auf diese Weise kann ich einen einzelnen Ausnahmetest testen, wenn beim Ausführen einer upload_part_copy ein Fehler auftritt
1. Versuch
import boto3
from mock import patch
with patch('botocore.client.S3.upload_part_copy', side_effect=Exception('Error Uploading')) as mock:
client = boto3.client('s3')
# Should return actual result
o = client.get_object(Bucket='my-bucket', Key='my-key')
# Should return mocked exception
e = client.upload_part_copy()
Dies führt jedoch zu folgendem Fehler:
ImportError: No module named S3
2. Versuch
Nachdem ich mir den Quellcode botocore.client.py angesehen hatte, stellte ich fest, dass er etwas Kluges tut und die Methode upload_part_copy
nicht existiert. Ich fand, dass es BaseClient._make_api_call
stattdessen zu rufen scheint, also versuchte ich das zu verspotten
import boto3
from mock import patch
with patch('botocore.client.BaseClient._make_api_call', side_effect=Exception('Error Uploading')) as mock:
client = boto3.client('s3')
# Should return actual result
o = client.get_object(Bucket='my-bucket', Key='my-key')
# Should return mocked exception
e = client.upload_part_copy()
Dies löst eine Ausnahme aus ... aber auf get_object
die ich vermeiden möchte.
Irgendwelche Ideen, wie ich nur die Ausnahme auf die upload_part_copy
Methode werfen kann ?
client
in das zu testende Gerät injiziert werden? Mein Verständnis von Pythonic-Unit-Tests war, dass Tester so etwas verwendenunittest.mock
, um importierte Abhängigkeiten zu verspotten. Verspottet dieser Ansatz Boto-Clients, die in andere Dateien importiert werden?Sobald ich hier gepostet habe, habe ich eine Lösung gefunden. Hier ist es hoffentlich hilft es :)
import botocore from botocore.exceptions import ClientError from mock import patch import boto3 orig = botocore.client.BaseClient._make_api_call def mock_make_api_call(self, operation_name, kwarg): if operation_name == 'UploadPartCopy': parsed_response = {'Error': {'Code': '500', 'Message': 'Error Uploading'}} raise ClientError(parsed_response, operation_name) return orig(self, operation_name, kwarg) with patch('botocore.client.BaseClient._make_api_call', new=mock_make_api_call): client = boto3.client('s3') # Should return actual result o = client.get_object(Bucket='my-bucket', Key='my-key') # Should return mocked exception e = client.upload_part_copy()
Jordan Philips hat auch eine großartige Lösung mit der Klasse botocore.stub.Stubber veröffentlicht . Während eine sauberere Lösung war ich nicht in der Lage, bestimmte Operationen zu verspotten.
quelle
Hier ist ein Beispiel für eine einfache Python-Unittest, mit der der API-Aufruf client = boto3.client ('ec2') gefälscht werden kann ...
import boto3 class MyAWSModule(): def __init__(self): client = boto3.client('ec2') tags = client.describe_tags(DryRun=False) class TestMyAWSModule(unittest.TestCase): @mock.patch("boto3.client.get_tags") @mock.patch("boto3.client") def test_open_file_with_existing_file(self, mock_boto_client, mock_describe_tags): mock_describe_tags.return_value = mock_get_tags_response my_aws_module = MyAWSModule() mock_boto_client.assert_call_once('ec2') mock_describe_tags.assert_call_once_with(DryRun=False) mock_get_tags_response = { 'Tags': [ { 'ResourceId': 'string', 'ResourceType': 'customer-gateway', 'Key': 'string', 'Value': 'string' }, ], 'NextToken': 'string' }
hoffentlich hilft das.
quelle
Wenn Sie weder
moto
den Botocore-Stubber noch den Botocore-Stubber verwenden möchten (der Stubber verhindert anscheinend nicht, dass HTTP-Anforderungen an AWS-API-Endpunkte gesendet werden ), können Sie die ausführlichere Methode unittest.mock verwenden:foo/bar.py
import boto3 def my_bar_function(): client = boto3.client('s3') buckets = client.list_buckets() ...
bar_test.py
import unittest from unittest import mock class MyTest(unittest.TestCase): @mock.patch('foo.bar.boto3.client') def test_that_bar_works(self, mock_s3_client): self.assertTrue(mock_s3_client.return_value.list_buckets.call_count == 1)
quelle
Was ist mit einfach Moto ?
Es kommt mit einem sehr praktischen Dekorateur :
from moto import mock_s3 @mock_s3 def test_my_model_save(): pass
quelle
Ich musste den
boto3
Client für einige Integrationstests verspotten und es war ein bisschen schmerzhaft! Das Problem, das ich hatte, ist, dassmoto
es nichtKMS
sehr gut unterstützt, aber ich wollte mein eigenes Modell für dieS3
Eimer nicht umschreiben . Also habe ich diesen Morph aller Antworten erstellt. Es funktioniert auch global, was ziemlich cool ist!Ich habe es mit 2 Dateien eingerichtet.
Der erste ist
aws_mock.py
. Für dieKMS
Verspottung habe ich einige vordefinierte Antworten erhalten, die vom Live-boto3
Client kamen.from unittest.mock import MagicMock import boto3 from moto import mock_s3 # `create_key` response create_resp = { ... } # `generate_data_key` response generate_resp = { ... } # `decrypt` response decrypt_resp = { ... } def client(*args, **kwargs): if args[0] == 's3': s3_mock = mock_s3() s3_mock.start() mock_client = boto3.client(*args, **kwargs) else: mock_client = boto3.client(*args, **kwargs) if args[0] == 'kms': mock_client.create_key = MagicMock(return_value=create_resp) mock_client.generate_data_key = MagicMock(return_value=generate_resp) mock_client.decrypt = MagicMock(return_value=decrypt_resp) return mock_client
Das zweite ist das eigentliche Testmodul. Nennen wir es
test_my_module.py
. Ich habe den Code von weggelassenmy_module
. Sowie Funktionen, die getestet werden. Lassen Sie uns die Teilnehmer anrufenfoo
,bar
Funktionen.from unittest.mock import patch import aws_mock import my_module @patch('my_module.boto3') def test_my_module(boto3): # Some prep work for the mock mode boto3.client = aws_mock.client conn = boto3.client('s3') conn.create_bucket(Bucket='my-bucket') # Actual testing resp = my_module.foo() assert(resp == 'Valid') resp = my_module.bar() assert(resp != 'Not Valid') # Etc, etc, etc...
Eine weitere Sache, nicht sicher, ob das behoben ist, aber ich fand heraus, dass dies
moto
nicht glücklich war, es sei denn, Sie haben einige Umgebungsvariablen wie Anmeldeinformationen und Region festgelegt. Sie müssen keine tatsächlichen Anmeldeinformationen sein, aber sie müssen festgelegt werden. Es besteht die Möglichkeit, dass dies behoben ist, wenn Sie dies lesen! Aber hier ist ein Code, falls Sie ihn brauchen, diesmal Shell-Code!export AWS_ACCESS_KEY_ID='foo' export AWS_SECRET_ACCESS_KEY='bar' export AWS_DEFAULT_REGION='us-east-1'
Ich weiß, dass es wahrscheinlich nicht der schönste Code ist, aber wenn Sie nach etwas Universellem suchen, sollte es ziemlich gut funktionieren!
quelle
@pytest.fixture
und@mock.patch
und es funktioniert. Ich wünschte, ich könnte Ihre Antwort mehr als einmal positiv bewerten. Es hat mir enorm geholfen, Boto3-Stubs auch für Kunden, die Stubs (noch) nicht unterstützen, konsequent zu verwenden.Hier ist meine Lösung zum Patchen eines Boto-Clients, der im Darm meines Projekts verwendet wird, mit
pytest
Fixtures. Ich verwende nur 'mturk' in meinem Projekt.Der Trick für mich bestand darin, einen eigenen Client zu erstellen und dann
boto3.client
mit einer Funktion zu patchen , die diesen vorab erstellten Client zurückgibt.@pytest.fixture(scope='session') def patched_boto_client(): my_client = boto3.client('mturk') def my_client_func(*args, **kwargs): return my_client with patch('bowels.of.project.other_module.boto3.client', my_client_func): yield my_client_func def test_create_hit(patched_boto_client): client = patched_boto_client() stubber = Stubber(client) stubber.add_response('create_hit_type', {'my_response':'is_great'}) stubber.add_response('create_hit_with_hit_type', {'my_other_response':'is_greater'}) stubber.activate() import bowels.of.project # this module imports `other_module` bowels.of.project.create_hit_function_that_calls_a_function_in_other_module_which_invokes_boto3_dot_client_at_some_point()
Ich definiere auch ein anderes Gerät, das Dummy-Aws-Creds einrichtet, damit Boto nicht versehentlich andere Anmeldeinformationen auf dem System abruft. Ich habe buchstäblich 'foo' und 'bar' als meine Creds zum Testen festgelegt - das ist keine Redaktion.
Es ist wichtig, dass
AWS_PROFILE
env nicht gesetzt ist, da Boto sonst nach diesem Profil sucht.@pytest.fixture(scope='session') def setup_env(): os.environ['AWS_ACCESS_KEY_ID'] = 'foo' os.environ['AWS_SECRET_ACCESS_KEY'] = 'bar' os.environ.pop('AWS_PROFILE', None)
Und dann
setup_env
gebe ich als Pytest-usefixtures
Eintrag an, damit er für jeden Testlauf verwendet wird.quelle