Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/278.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
模仿boto3 S3客户端方法Python_Python_Mocking_Boto_Boto3_Botocore - Fatal编程技术网

模仿boto3 S3客户端方法Python

模仿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

我试图模拟来自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.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
模拟,我得到了一些来自live
bot3
客户端的预定义响应

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
指定为pytest
usefixtures
条目,以便在每次测试运行中都使用它。

好吧,因为它在botocore中,所以您必须查看botocore文档,而这样做的人并不多。也是最近的。为什么client.upload_part_copy()会引发ClientError?@AidanMelen,因为我在响应队列中显式添加了一个错误。您还可以添加正常的服务响应。我将更新以显示两者。是否需要将
client
注入被测单元?我对Pythonic单元测试的理解是,测试人员使用类似于
unittest.mock的东西来模拟导入的依赖项。这种方法会模拟在其他文件中导入的boto客户机吗?我收到一个错误消息说,
{NoCredentialsError}找不到凭据
,这非常有用。我花了一段时间才意识到,很多boto3客户端都是有效的,因此不能直接模拟。这是一个解决方案,对我来说,作为Stubber和许多其他模拟工具都无法对boto3定制功能(如上传文件或生成预签名URL)进行存根。这个答案非常好。我第一次尝试使用stubb