在 pytest MagicMock 中正确模拟 gcs Bucket 和 Blob

问题描述 投票:0回答:1

我正在尝试为此函数编写测试。我不想依赖真正的 gcs 对象,而是模拟对象。

# gcs_blobs.py file
from google.cloud import storage
        
    def check_existing_blobs(new_blob_names: list[str], bucket: storage.Bucket) -> list[storage.Blob]:
        # Check if any of the new blobs exists already on bucket
        current_blobs = [blob for blob in bucket.list_blobs()]
        existing_blobs = []
        for blob_name in new_blob_names:
            for blob in current_blobs:
                if blob_name == blob.name:
                    existing_blobs.append(blob)
        return existing_blobs

据我正确理解,我应该

  • 模拟 Bucket.list_blobs() 方法,以便它返回模拟的 Blob 对象列表
  • 模拟 Blob 实例“名称”属性,以便它返回我想要的名称

我正在尝试这样做,但没有成功。在下面的测试代码中,

check_existing_blobs(new_blob_names, bucket)
返回空列表。在这种情况下,我希望它返回名为 blob1 的 blob。 我该如何正确地做到这一点?

在 blob.name 上使用“side_effect”是否允许我在连续调用 blob.name 时从提供的“existing_blob_names”列表中返回名称?

如何使用具有列表中名称的 blob 列表正确执行此断言?

# test_blobs.py file
from unittest.mock import Mock, MagicMock

import pytest
from google.cloud import storage

from .gcs_files import (
    check_existing_blobs
)


@pytest.mark.parametrize(
    "new_blob_names,existing_blob_names",
    [
        (
            ['blob1'],
            ['blob1', 'blob2'],
        ),
    ]
)
def test_contains_existing_files(mocker, new_blob_names, existing_blob_names):
    
    storage.Bucket = MagicMock()
    bucket = storage.Bucket()

    storage.Blob = MagicMock()
    blob = storage.Blob()
    
    #mocker.patch.object(blob, "name", new="blob_name") # after that blob.name prints "blob_name"
    mocker.patch.object(blob, "name", side_effect=existing_blob_names)

    print(blob.name) # output: <MagicMock name='name' id='139862698975120'>

    # in place of '???' it should be mock of storage.Blob instance which .name returns one of the blob names
    # assert check_existing_blobs(new_blob_names, bucket) == [???] 


还请告诉我是否应该将此问题拆分为子问题。

unit-testing mocking google-cloud-storage pytest pytest-mock
1个回答
0
投票

我建议不要为此使用

MagicMock()
。相反,只需创建一个具有您想要的行为的新类即可:

class MockBucket:

    def __init__(self, names):
        self.blobs = [MockBlob(x) for x in names]

    def list_blobs(self):
        return self.blobs

class MockBlob:

    def __init__(self, name):
        self.name = name

我还建议不要分配给

storage.Bucket
storage.Blob
。当测试功能结束时,这些分配不会被恢复,因此它们很有可能干扰以后的测试。事实上,
mocker.patch
mocker.patch.object
的目的就是为了避免这种赋值。但就您而言,没有理由修补任何内容,因为正在测试的代码不依赖于任何全局变量。

© www.soinside.com 2019 - 2024. All rights reserved.