模仿boto3 S3客户端方法Python
我试图模拟来自boto3 s3客户机对象的singluar方法以引发异常。但是我需要这个类的所有其他方法才能正常工作 这样我就可以在执行测试时发生错误时测试单一异常测试 第一次尝试模仿boto3 S3客户端方法Python,python,mocking,boto,boto3,botocore,Python,Mocking,Boto,Boto3,Botocore,我试图模拟来自boto3 s3客户机对象的singluar方法以引发异常。但是我需要这个类的所有其他方法才能正常工作 这样我就可以在执行测试时发生错误时测试单一异常测试 第一次尝试 import boto3 from mock import patch with patch('botocore.client.S3.upload_part_copy', side_effect=Exception('Error Uploading')) as mock: client = boto3.cl
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()
但是,这会产生以下错误:
ImportError: No module named S3
第二次尝试
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()
在查看botocore.client.py源代码后,我发现它做了一些巧妙的事情,而且方法upload\u part\u copy
不存在。我发现它似乎调用了BaseClient.\u make\u api\u call
,所以我试图模仿它
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()
这会引发异常。。。但是在我想要避免的get_对象上
关于如何只能在upload\u part\u copy
方法上抛出异常的任何想法?我一发布到这里,就设法想出了一个解决方案。这里是希望它有帮助:)
在类中使用。虽然是一个更清洁的解决方案,但我无法模拟具体的操作 Botocore有一个客户端存根,您可以用于以下目的:
下面是一个将错误放入的示例:
import boto3
from botocore.stub import Stubber
client = boto3.client('s3')
stubber = Stubber(client)
stubber.add_client_error('upload_part_copy')
stubber.activate()
# Will raise a ClientError
client.upload_part_copy()
下面是一个将正常响应放入的示例。此外,stubber现在可以在上下文中使用。需要注意的是,stuber将尽可能验证您提供的响应是否与服务实际返回的内容匹配。这并不完美,但它可以保护您避免插入完全无意义的响应
import boto3
from botocore.stub import Stubber
client = boto3.client('s3')
stubber = Stubber(client)
list_buckets_response = {
"Owner": {
"DisplayName": "name",
"ID": "EXAMPLE123"
},
"Buckets": [{
"CreationDate": "2016-05-25T16:55:48.000Z",
"Name": "foo"
}]
}
expected_params = {}
stubber.add_response('list_buckets', list_buckets_response, expected_params)
with stubber:
response = client.list_buckets()
assert response == list_buckets_response
下面是一个简单python单元测试的示例,可用于伪造client=boto3.client('ec2')api调用
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'
}
希望这能有所帮助。简单地使用它怎么样
它有一个非常方便的:
如果您不想使用moto
或botocore stubber(stubber似乎不阻止向AWS API端点发出HTTP请求),可以使用更详细的unittest.mock方法:
foo/bar.py
import boto3
def my_bar_function():
client = boto3.client('s3')
buckets = client.list_buckets()
...
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)
bar_test.py
import boto3
def my_bar_function():
client = boto3.client('s3')
buckets = client.list_buckets()
...
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)
我不得不模拟bot3
客户端进行一些集成测试,这有点痛苦!我遇到的问题是moto
不太支持KMS
,但我不想为S3
bucket重写我自己的mock。所以我创造了所有答案的变形。它还可以在全球范围内运行,非常酷
我有它的设置与2个文件
第一个是aws\u mock.py
。对于KMS
模拟,我得到了一些来自livebot3
客户端的预定义响应
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
第二个是实际测试模块。让我们称之为test\u my\u module.py
。我省略了我的\u模块的代码
。以及正在测试的功能。让我们调用那些foo
,bar
函数
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...
还有一件事,我不确定这是否是固定的,但我发现moto
并不快乐,除非您设置一些环境变量,如凭证和区域。它们不必是实际凭证,但需要设置。有一个机会,它可能是固定的时候,你读这篇文章!但是这里有一些代码,以防您需要它,这次是shell代码
export AWS_ACCESS_KEY_ID='foo'
export AWS_SECRET_ACCESS_KEY='bar'
export AWS_DEFAULT_REGION='us-east-1'
我知道这可能不是最漂亮的一段代码,但是如果你正在寻找一些通用的东西,它应该工作得很好 这是我的解决方案,用于使用pytest
fixture修补项目内部使用的boto客户端。我只在我的项目中使用“mturk”
我的诀窍是创建自己的客户机,然后使用返回预先创建的客户机的函数修补bot3.client
@pytest.fixture(scope='session')
def已修补的\u boto\u客户端():
my_client=bot3.client('mturk'))
定义我的客户函数(*args,**kwargs):
归还我的客户
使用补丁('bowels.of.project.other\u module.boto3.client',my\u client\u func):
交出我的客户函数
def测试\u创建\u命中(已修补的\u boto\u客户端):
客户端=已修补的\u boto\u客户端()
stubber=stubber(客户端)
add_response('create_hit_type',{'my_response':'is_great'})
add_response('create_hit_with_hit_type',{'my_other_response':'is_more'})
stuber.activate()
导入bowels.of.project#此模块导入“其他”模块`
bowels.of.project.create_hit_function_调用_other_模块中的_a_function_,该模块在某个点调用_boto3_dot_client_()
我还定义了另一个装置,用于设置虚拟aws凭据,以便boto不会意外地在系统上获取其他凭据集。我真的把“foo”和“bar”作为我的测试信条——这不是修订
重要的是,AWS\u PROFILE
env必须取消设置,否则boto将查找该PROFILE
@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)
然后我将
setup\u env
指定为pytestusefixtures
条目,以便在每次测试运行中都使用它。好吧,因为它在botocore中,所以您必须查看botocore文档,而这样做的人并不多。也是最近的。为什么client.upload_part_copy()会引发ClientError?@AidanMelen,因为我在响应队列中显式添加了一个错误。您还可以添加正常的服务响应。我将更新以显示两者。是否需要将client
注入被测单元?我对Pythonic单元测试的理解是,测试人员使用类似于unittest.mock的东西来模拟导入的依赖项。这种方法会模拟在其他文件中导入的boto客户机吗?我收到一个错误消息说,{NoCredentialsError}找不到凭据
,这非常有用。我花了一段时间才意识到,很多boto3客户端都是有效的,因此不能直接模拟。这是一个解决方案,对我来说,作为Stubber和许多其他模拟工具都无法对boto3定制功能(如上传文件或生成预签名URL)进行存根。这个答案非常好。我第一次尝试使用stubb