在 Python unittest 中模拟和修补 keystoneclient
Mocking and patching keystoneclient in Python unittest
我正在为 class 编写单元测试,如下所示。我正在尝试断言是否使用补丁 keystoneclient 正确调用了日志记录。 class 如下所示。问题是,我无法通过 for 语句并且即使在修补 CredentialManager 之后也永远无法到达 LOGGER.warning 或 LOGGER.info。我是整个单元测试和模拟的新手,所以我可能没有清楚地理解某些东西。
from keystoneclient.v3.client import Client
from keystoneclient.v3.credentials import CredentialManager
import logging
LOGGER = logging.getLogger(__name__)
class MyClass(object):
def __init__(self):
...
def myfunc(self):
new_credentials = {}
client = Client(
username=self.username,
password=self.password,
auth_url=self.auth_url,
user_domain_name=self.user_domain_name,
domain_name=self.domain_name
)
abc = CredentialManager(client)
for credential in abc.list():
defg = str(credential.type)
(access, secret) = _anotherfunc(credential.blob)
if not defg:
LOGGER.warning('no abc')
if defg in new_credentials:
LOGGER.info('Ignoring duplate')
new_credentials[defg] = (access, secret)
我的单元测试看起来像这样,
import unittest
from mock import patch, MagicMock
import MyClass
LOGGER = logging.getLogger('my_module')
@patch('MyClass.Client', autospec = True)
@patch('MyClass.CredentialManager', autospec = True)
class TestMyClass(unittest.TestCase):
def test_logger_warning(self,,mock_client, mock_cm):
with patch.object(LOGGER, 'warning') as mock_warning:
obj = MyClass()
mock_warning.assert_called_with('no abc')
我得到的错误看起来像这样。
for credential in abc.list():
AttributeError: 'tuple' object has no attribute 'list'
因此,即使在使用 autospect 修补 CredentialManager 之后,我仍然在 abc.list() 上遇到错误。我需要达到可以测试 LOGGER 的地步,但似乎此修补程序不适用于 list()。我应该如何让这个错误消失并能够通过 for 语句以便我可以在日志记录时断言?
这个答案要涵盖多少细节:
- 补丁顺序:
@patch
装饰器作为堆栈应用,这意味着第一个装饰器 -> 最后一个模拟参数
- 如果你想要
CredentialManager().list()
return 包含空 type
的内容,你应该使用你的 mock_cm
来完成它
- 如果你想测试一些东西,你应该打电话给
obj.myfunc()
:)
代码:
导入单元测试
来自模拟导入补丁,Mock
导入 MyClass
@patch('MyClass.Client', autospec=True)
@patch('MyClass.CredentialManager', autospec=True)
class TestMyClass(unittest.TestCase):
#Use decorator for test case too... is simpler and neat
@patch("MyClass.LOGGER", autospec=True)
def test_logger_warning(self, mock_logger, mock_cm, mock_client):
#pay attention to the mock argument order (first local and the the stack of patches
# Extract the mock that will represent you abc
mock_abc = mock_cm.return_value
# Build a mock for credential with desidered empty type
mock_credential = Mock(type="")
# Intrument list() method to return your credential
mock_abc.list.return_value = [mock_credential]
obj = MyClass.MyClass()
# Call the method
obj.myfunc()
# Check your logger
mock_logger.warning.assert_called_with('no abc')
对所有补丁使用 autospec=True
的好处:这是一个很好的做法。
无论如何,我想鼓励您将日志记录部分提取到一个方法中,并通过一些伪造的凭据对其进行测试:更简单、更好的设计:类似于
def myfunc(self):
new_credentials = {}
client = Client(
username=self.username,
password=self.password,
auth_url=self.auth_url,
user_domain_name=self.user_domain_name,
domain_name=self.domain_name
)
abc = CredentialManager(client)
for credential in abc.list():
self.process_credential(credential, new_credentials)
@staticmethod
def process_credential(credential, cache):
(access, secret) = _anotherfunc(credential.blob)
defg = str(credential.type)
MyClass.logging_credential_process(credential, not defg, defg in cache)
cache[defg] = (access, secret)
@staticmethod
def logging_credential_process(credential, void_type, duplicate):
if void_type:
LOGGER.warning('no abc')
if duplicate:
LOGGER.info('Ignoring duplate')
测试更简单,看起来更好。
我正在为 class 编写单元测试,如下所示。我正在尝试断言是否使用补丁 keystoneclient 正确调用了日志记录。 class 如下所示。问题是,我无法通过 for 语句并且即使在修补 CredentialManager 之后也永远无法到达 LOGGER.warning 或 LOGGER.info。我是整个单元测试和模拟的新手,所以我可能没有清楚地理解某些东西。
from keystoneclient.v3.client import Client
from keystoneclient.v3.credentials import CredentialManager
import logging
LOGGER = logging.getLogger(__name__)
class MyClass(object):
def __init__(self):
...
def myfunc(self):
new_credentials = {}
client = Client(
username=self.username,
password=self.password,
auth_url=self.auth_url,
user_domain_name=self.user_domain_name,
domain_name=self.domain_name
)
abc = CredentialManager(client)
for credential in abc.list():
defg = str(credential.type)
(access, secret) = _anotherfunc(credential.blob)
if not defg:
LOGGER.warning('no abc')
if defg in new_credentials:
LOGGER.info('Ignoring duplate')
new_credentials[defg] = (access, secret)
我的单元测试看起来像这样,
import unittest
from mock import patch, MagicMock
import MyClass
LOGGER = logging.getLogger('my_module')
@patch('MyClass.Client', autospec = True)
@patch('MyClass.CredentialManager', autospec = True)
class TestMyClass(unittest.TestCase):
def test_logger_warning(self,,mock_client, mock_cm):
with patch.object(LOGGER, 'warning') as mock_warning:
obj = MyClass()
mock_warning.assert_called_with('no abc')
我得到的错误看起来像这样。
for credential in abc.list():
AttributeError: 'tuple' object has no attribute 'list'
因此,即使在使用 autospect 修补 CredentialManager 之后,我仍然在 abc.list() 上遇到错误。我需要达到可以测试 LOGGER 的地步,但似乎此修补程序不适用于 list()。我应该如何让这个错误消失并能够通过 for 语句以便我可以在日志记录时断言?
这个答案要涵盖多少细节:
- 补丁顺序:
@patch
装饰器作为堆栈应用,这意味着第一个装饰器 -> 最后一个模拟参数 - 如果你想要
CredentialManager().list()
return 包含空type
的内容,你应该使用你的mock_cm
来完成它 - 如果你想测试一些东西,你应该打电话给
obj.myfunc()
:)
代码:
导入单元测试 来自模拟导入补丁,Mock 导入 MyClass
@patch('MyClass.Client', autospec=True)
@patch('MyClass.CredentialManager', autospec=True)
class TestMyClass(unittest.TestCase):
#Use decorator for test case too... is simpler and neat
@patch("MyClass.LOGGER", autospec=True)
def test_logger_warning(self, mock_logger, mock_cm, mock_client):
#pay attention to the mock argument order (first local and the the stack of patches
# Extract the mock that will represent you abc
mock_abc = mock_cm.return_value
# Build a mock for credential with desidered empty type
mock_credential = Mock(type="")
# Intrument list() method to return your credential
mock_abc.list.return_value = [mock_credential]
obj = MyClass.MyClass()
# Call the method
obj.myfunc()
# Check your logger
mock_logger.warning.assert_called_with('no abc')
对所有补丁使用 autospec=True
的好处:这是一个很好的做法。
无论如何,我想鼓励您将日志记录部分提取到一个方法中,并通过一些伪造的凭据对其进行测试:更简单、更好的设计:类似于
def myfunc(self):
new_credentials = {}
client = Client(
username=self.username,
password=self.password,
auth_url=self.auth_url,
user_domain_name=self.user_domain_name,
domain_name=self.domain_name
)
abc = CredentialManager(client)
for credential in abc.list():
self.process_credential(credential, new_credentials)
@staticmethod
def process_credential(credential, cache):
(access, secret) = _anotherfunc(credential.blob)
defg = str(credential.type)
MyClass.logging_credential_process(credential, not defg, defg in cache)
cache[defg] = (access, secret)
@staticmethod
def logging_credential_process(credential, void_type, duplicate):
if void_type:
LOGGER.warning('no abc')
if duplicate:
LOGGER.info('Ignoring duplate')
测试更简单,看起来更好。