在Python unittest中模拟和修补keystoneclient

时间:2015-04-08 04:07:01

标签: python unit-testing mocking python-unittest magicmock

我正在编写一个类似于下面的类。我试图断言使用补丁keystoneclient正确调用日志记录。该课程如下所示。问题是,我无法通过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 statment以便我可以在记录时断言?

1 个答案:

答案 0 :(得分:1)

在这个答案中要涵盖多少细节:

  1. 补丁顺序:@patch装饰器作为堆栈应用,这意味着第一个装饰器 - >最后一个模拟参数
  2. 如果您希望CredentialManager().list()返回包含空type的内容,则应该检验mock_cm
  3. 如果你想测试一下,你应该致电obj.myfunc()。)
  4. 代码:

    导入unittest 来自模拟导入补丁,模拟 导入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')
    

    更容易测试并且看起来更好。