[英]Mocking AWS lambda and boto3
我有一個lambda(python),它返回客戶檔案的json表示。 它通過從頂級帳戶json開始,然后讀取鏈接的json文件直到它用完鏈接來完成此操作。 從s3讀取的函數是遞歸的,但遞歸只會是一個深度。
這是實際從給定鍵獲取json內容的方法。 (斗已知)
def get_index_from_s3(key):
try:
response = s3.get_object(
Bucket=bucket,
Key=key
)
body = response.get('Body')
content = body.read().decode('utf-8')
except ClientError as ex:
# print 'EXCEPTION MESSAGE: {}'.format(ex.response['Error']['Code'])
content = '{}'
message = json.loads(content)
return message
代碼返回在指定鍵處找到的json,或者在get_object由於ClientError(由NoSuchKey產生的結果)而失敗的情況下返回空字典。
我測試了這個,它的確有效。 對函數的第一次調用獲得了一大塊json。 解析json,找到鏈接,進行第二次調用,然后構建配置文件。 如果我刪除鏈接鍵上的對象,我只是按預期獲得默認的空表示。
我的問題來自測試這個。 我寫了幾個測試類,每個測試類都有一個編排方法,他們共享一個act方法。
為了我的快樂道路,我使用以下安排:
def arrange(self):
super(WhenCognitoAndNerfFoundTestCase, self).arrange()
# self.s3_response = self.s3.get_object.return_value
self.s3_body = self.s3.get_object.return_value.get.return_value
self.s3_body.read.return_value.decode.side_effect = [
self.cognito_content,
self.nerf_content]
signed_url = "https://this.is/a/signed/url/index.html"
self.s3.generate_presigned_url.return_value = signed_url
這正是我想要的。 s3_response是get_object的return_value,它具有get返回的Body屬性,后續讀取值返回json字符串。 我使用side_effect設置為json字符串列表,這樣我就可以在每次調用時返回一個不同的字符串(只有兩個) content = body.read().decode('utf-8')
但是當我想測試第二桶中缺少內容的情況時,我受到了阻礙。 我目前對這種安排的嘗試如下:
def arrange(self):
super(WhenCognitoOnlyFoundTestCase, self).arrange()
# self.s3_response = MagicMock()
# botocore.response.StreamingBody
self.s3.get_object.side_effect = [{},
ClientError]
# self.s3_response = self.s3.get_object.return_value
self.s3_body = self.s3.get_object.return_value.get.return_value
self.s3_body.read.return_value.decode.return_value = \
self.cognito_content
運行測試會產生以下結果:
def get_index_from_s3(key):
try:
response = s3.get_object(
Bucket=bucket,
Key=key
)
body = response.get('Body')
> content = body.read().decode('utf-8')
E AttributeError: 'NoneType' object has no attribute 'read'
master_profile.py:66: AttributeError
這是有道理的,因為read方法位於s3.get_object響應的Body屬性上,在此方案中為None。
所以我的問題是,我如何模仿這個東西,以便我可以測試它? 模擬get_object響應的難點在於,盡管它只是一個字典,但Body屬性是一個botocore.response.StreamingBody
,我不知道如何模擬。
根據經驗,您的目標應該是讓您的問題自成一體。 為了說明你做錯的一些事情,我稍微修改了你的初始函數,使它自成一體。
讓我們s3_module
我們要測試的s3_module
定義如下:
import boto3
from botocore.exceptions import ClientError
import json
s3 = boto3.client('s3')
def get_index_from_s3(key):
try:
response = s3.get_object(
Bucket='bucket',
Key=key
)
body = response.get('Body')
content = body.read().decode('utf-8')
except ClientError as ex:
import ipdb; ipdb.set_trace()
# print 'EXCEPTION MESSAGE: {}'.format(ex.response['Error']['Code'])
content = '{}'
message = json.loads(content)
return message
為了測試它,我們可以編寫另一個模塊s3_test
,其測試類似於:
import pytest
from unittest.mock import patch, Mock, MagicMock
from botocore.exceptions import ClientError
import json
from s3_module import get_index_from_s3
@patch('s3_module.s3.get_object')
def test_get_index_from_s3(s3_get_mock):
body_mock = Mock()
body_mock.read.return_value.decode.return_value = json.dumps('first_response')
s3_get_mock.side_effect = [{'Body': body_mock}, ClientError(MagicMock(), MagicMock())]
first_response = get_index_from_s3('key1')
assert first_response == 'first_response'
second_response = get_index_from_s3('key2')
assert second_response == {}
與您的解決方案相比,您錯過了一些要點:
self.s3.get_object.side_effect
應返回第一個響應的對象,該響應與其余代碼一起使用,即包含Body
鍵的字典,其內容可以read()
, decoded()
並由json.load()
self.s3.get_object.side_effect
應返回為第二個響應正確構造的ClientError
異常
您可以在botocore文檔中查看有關如何構建ClientError
異常的更多信息: http ://botocore.readthedocs.io/en/latest/client_upgrades.html#error-handling
您可以在文檔中找到有關修補和模擬的更多信息: https : //docs.python.org/3/library/unittest.mock.html 。
通常關於補丁的位置部分非常有用: https : //docs.python.org/3/library/unittest.mock.html#where-to-patch
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.