模拟boto3 S3客户端方法Python

pti*_*son 51 python mocking boto botocore boto3

我正在尝试从boto3 s3客户端对象模拟一个单一方法来抛出异常.但我需要这个类的所有其他方法正常工作.

这样我可以在执行upload_part_copy时测试单个异常测试并发生错误

第一次尝试

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()
Run Code Online (Sandbox Code Playgroud)

但是,这会产生以下错误:

ImportError: No module named S3
Run Code Online (Sandbox Code Playgroud)

第二次尝试

在查看了botocore.client.py源代码后,我发现它正在做一些聪明的事情并且该方法upload_part_copy不存在.我发现它似乎在调用,BaseClient._make_api_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()
Run Code Online (Sandbox Code Playgroud)

这引发了一个例外......但是get_object我要避免它.

关于我如何只能在upload_part_copy方法上抛出异常的任何想法?

Jor*_*ips 72

Botocore有一个客户端存根,你可以用它来实现这个目的:docs.

以下是将错误放入的示例:

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()
Run Code Online (Sandbox Code Playgroud)

这是一个将正常响应放入其中的示例.此外,现在可以在上下文中使用存根.重要的是要注意,存根将尽可能验证您提供的响应是否与服务实际返回的响应相匹配.这并不完美,但它可以保护您不会插入完全无意义的回复.

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
Run Code Online (Sandbox Code Playgroud)

  • 使用此存根是一个坏主意,因为它会在“~/.aws”目录中不存在配置文件的 CI 环境中失败。它适用于没有这些文件的 S3,但不适用于大多数其他 AWS 服务。因此,我建议不要使用存根。 (3认同)
  • 为什么client.upload_part_copy()会引发ClientError? (2认同)
  • 是否需要将"客户"注入被测单元?我对Pythonic单元测试的理解是,测试人员使用类似`unittest.mock`来模拟导入的依赖项.这种方法会模拟boto在其他文件中导入的客户端吗? (2认同)
  • 这意味着存根程序没有拦截客户端调用,而是尝试进行实际的 Boto 调用 (2认同)

pti*_*son 22

我一发布在这里,我设法找到了解决方案.这是希望它有帮助:)

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()
Run Code Online (Sandbox Code Playgroud)

Jordan Philips还使用botocore.stub.Stubber发布了一个很棒的解决方案.虽然更清洁的解决方案,我无法模拟特定的操作.

  • 这非常有帮助.我花了一段时间才意识到很多boto3客户端都是[在运行时生成](https://boto3.readthedocs.io/en/latest/guide/new.html#major-features),因此,不能直接嘲笑. (4认同)
  • 这个答案很棒。我首先尝试使用存根,但它似乎只适用于立即调用,由于某种原因,我无法让它适用于子函数内的调用。另一方面,这工作得很好并且很容易实现,所以谢谢! (2认同)

c4u*_*elf 10

如果您不想使用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()
    ...
Run Code Online (Sandbox Code Playgroud)

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)

Run Code Online (Sandbox Code Playgroud)


Aid*_*len 6

这是一个简单的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_boto_client.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'
}
Run Code Online (Sandbox Code Playgroud)

希望这会有所帮助.

  • 'test_open_file_with_existing_file' 的第一行不应该是 'mock_describe_tags.return_value = mock_get_tags_response'?而不是 'mock_boto_client' ? (3认同)

wik*_*ier 6

那么简单地使用moto呢?

它带有一个非常方便的装饰器

from moto import mock_s3

@mock_s3
def test_my_model_save():
    pass
Run Code Online (Sandbox Code Playgroud)

  • 如果我的客户在全球该怎么办?那个时候在文件导入的时候,会调用原来的AWS Infra吧?有什么解决办法吗? (2认同)