Как совместить наследование с макетом autospe c in Python - PullRequest
0 голосов
/ 30 января 2020

Проблема

Я ищу способ правильно смоделировать объекты в своих модульных тестах, но у меня возникают проблемы с получением unittest.mock.create_autospec или unittest.mock.Mock для выполнения того, что мне нужно. Я думаю, что мне нужно использовать наследование с объектом Mock, но у меня возникают проблемы с тем, чтобы он работал.

Изображение, которое мне нужно, чтобы смоделировать классы в стороннем модуле, который выглядит примерно так (притворяются, что строки с raise NotImplementedError являются внешние вызовы API, которые я хочу избегать в своих модульных тестах):

class FileStorageBucket():
    def __init__(self, bucketname: str) -> None:
        self.bucketname = bucketname

    def download(self, filename) -> None:
        raise NotImplementedError

    # ...lots more methods...


class FileStorageClient():
    def auth(self, username: str, password: str) -> None:
        raise NotImplementedError

    def get_bucket(self, bucketname: str) -> FileStorageBucket:
        raise NotImplementedError
        return FileStorageBucket(bucketname)

    # ...lots more methods...

И он может использоваться в другом месте моего приложения, например:

client = FileStorageClient()
client.auth("me", "mypassword")
client.get_bucket("my-bucket").download("my-file.jpg")

Если я заменив FileStorageClient на объект Mock, я бы хотел выяснить, выполняет ли мой модульный тест какой-либо код, где:

  • Методы, которые не существуют ни в FileStorageClient, ни в FileStorageBucket называются
  • Методы, которые существуют в FileStorageClient или FileStorageBucket, вызываются с неправильными аргументами

Так, client.get_bucket("foo").download() должно вызвать исключение, что имя файла обязательный аргумент для .download().

Вещи, которые я пробовал:

Сначала я попытался использовать create_autospec. Он может отлавливать некоторые типы ошибок:

>>> MockClient = create_autospec(FileStorageClient)
>>> client = MockClient()
>>> client.auth(user_name="name", password="password")
TypeError: missing a required argument: 'username'

Но, конечно, поскольку он не знает тип возвращаемого значения, который должен иметь get_bucket, он не отлавливает другие типы ошибок:

>>> MockClient = create_autospec(FileStorageClient)
>>> client = MockClient()
>>> client.get_bucket("foo").download(wrong_arg="foo")
<MagicMock name='mock.get_bucket().download()' id='4554265424'>

Я думал, что смогу решить эту проблему, создав классы, унаследованные от вывода create_autospec:

class MockStorageBucket(create_autospec(FileStorageBucket)):
    def path(self, filename) -> str:
        return f"/{self.bucketname}/{filename}"


class MockStorageClient(create_autospec(FileStorageClient)):
    def get_bucket(self, bucketname: str):
        bucket = MockStorageBucket()
        bucket.bucketname = bucketname
        return bucket

Но на самом деле он не возвращает экземпляр MockStorageBucket, как ожидалось:

>>> client = MockStorageClient()
>>> client.get_bucket("foo").download(wrong_arg="foo")
<MagicMock name='mock.get_bucket().download()' id='4554265424'>

Итак, я попытался унаследовать от Mock и вручную установить "spe c" в init:

class MockStorageBucket(Mock):
    def __init__(self, *args, **kwargs):
        # Pass `FileStorageBucket` as the "spec"
        super().__init__(FileStorageBucket, *args, **kwargs)

    def path(self, filename) -> str:
        return f"/{self.bucketname}/{filename}"


class MockStorageClient(Mock):
    def __init__(self, *args, **kwargs):
        # Pass `FileStorageClient` as the "spec"
        super().__init__(FileStorageClient, *args, **kwargs)

    def get_bucket(self, bucketname: str):
        bucket = MockStorageBucket()
        bucket.bucketname = bucketname
        return bucket

Теперь метод get_bucket возвращает MockStorageBucket экземпляр, как и ожидалось, и я могу обнаружить некоторые ошибки, такие как доступ к атрибутам, которые не существуют:

>>> client = MockStorageClient()
>>> client.get_bucket("my-bucket")
<__main__.FileStorageBucket at 0x10f7a0110>
>>> client.get_bucket("my-bucket").foobar
AttributeError: Mock object has no attribute 'foobar'

Однако, в отличие от экземпляра Mock, созданного с create_autospec Образцы Mock с Mock(spec=whatever) не появляется, чтобы проверить, что правильные аргументы передаются функции:

>>> client.auth(wrong_arg=1)
<__main__.FileStorageClient at 0x10dac5990>

Ответы [ 2 ]

1 голос
/ 11 февраля 2020

Просто установите return_value в вашем методе get_bucket, чтобы он был другим макетом с другим значением c. Вам не нужно возиться с созданием MockStorageBucket и MockStorageClient.

mock_client = create_autospec(FileStorageClient, spec_set=True)
mock_bucket = create_autospec(FileStorageBucket, spec_set=True)
mock_client.get_bucket.return_value = mock_bucket

mock_client.get_bucket("my-bucket").download("my-file.jpg")
0 голосов
/ 11 февраля 2020

Я думаю, что полный код, который я хочу, выглядит примерно так:

def mock_client_factory() -> Mock:
    MockClient = create_autospec(FileStorageClient)

    def mock_bucket_factory(bucketname: str) -> Mock:
        MockBucket = create_autospec(FileStorageBucket)
        mock_bucket = MockBucket(bucketname=bucketname)
        mock_bucket.bucketname = bucketname
        return mock_bucket

    mock_client = MockClient()
    mock_client.get_bucket.side_effect = mock_bucket_factory
    return mock_client
...