Проблема
Я ищу способ правильно смоделировать объекты в своих модульных тестах, но у меня возникают проблемы с получением 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>