Насмешка и исправление keystoneclient в модульном тесте Python

Я пишу unittest для класса, как показано ниже. Я пытаюсь подтвердить, правильно ли ведение журнала вызывается с патчем keystoneclient. Класс выглядит следующим образом. Проблема в том, что я не могу пройти через оператор for и никогда не могу получить доступ к LOGGER.warning или LOGGER.info даже после исправления CredentialManager. Я новичок во всем unittest и Mock, поэтому, возможно, я что-то не понимаю.

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'

Поэтому даже после исправления CredentialManager с помощью autospect я получаю сообщение об ошибке в abc.list(). Мне нужно добраться до точки, где я могу протестировать LOGGER, но похоже, что это исправление не работает для list(). Как мне сделать так, чтобы эта ошибка исчезла и чтобы я мог пройти через статус for, чтобы я мог утверждать при регистрации?


person James C.    schedule 08.04.2015    source источник
comment
Можете ли вы исправить код ifs в for цикле не имеет смысла. Я могу подать ответ, но мне нужно точно понять, что нужно написать тест: в вашем случае abc не может быть False в цикле.   -  person Michele d'Amico    schedule 08.04.2015
comment
Извините, я отредактировал код. Это должно было быть defg вместо abc в операторе if. Я пытаюсь написать модульный тест, чтобы подтвердить, успешно ли вызывается журнал.   -  person James C.    schedule 08.04.2015
comment
Какие версии Python и mock вы используете?   -  person Michele d'Amico    schedule 08.04.2015
comment
Python 2.7.5 и макет 1.0.1   -  person James C.    schedule 10.04.2015


Ответы (1)


Сколько деталей нужно охватить в этом ответе:

  1. Порядок исправлений: @patch декоратор применяется как стек, это означает, что первый декоратор -> последний фиктивный аргумент
  2. Если вы хотите, чтобы CredentialManager().list() возвращало что-то, содержащее пустое type, вы должны настроить свой mock_cm для этого.
  3. Вы должны позвонить obj.myfunc(), если хотите что-то проверить :)

Код:

импортировать 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')

проще тестировать и выглядит лучше.

person Michele d'Amico    schedule 08.04.2015
comment
Большое спасибо. Я смог заставить его работать, но есть вопрос. Почему я не могу использовать mock_cm.return_value или mock_cm.list()? List() находится в классе CredentialManager, но меня несколько смущает, почему мы должны извлекать его в mock_abc и использовать mock_abc.list.return_value. - person James C.; 10.04.2015
comment
@killahjc mock_cm — макет класса CredentialManager. Когда ваш производственный код использует abc=CredentialManager(client), abc становится return_value из mock_cm. Позже вы используете list() из abc, а не из CredentialManager класса. Наконец, чтобы установить return_value list, вы должны установить возвращаемое значение макета list экземпляра abc (так что mock_cm.return_value)... Еще одно... вы забыли проголосовать/принять ответ :) - person Michele d'Amico; 11.04.2015
comment
Большое спасибо еще раз :) - person James C.; 11.04.2015