Как мне издеваться над частью конструктора Python только для тестирования?

Я новичок в Python, поэтому прошу прощения, если это дубликат или слишком простой вопрос. Я написал класс-координатор, который вызывает два других класса, использующих библиотеку kafka-python для отправки/чтения данных из Kafka. Я хочу написать модульный тест для своего класса координатора, но мне трудно понять, как лучше всего это сделать. Я надеялся, что смогу создать альтернативный конструктор, в который я мог бы передать свои издевательские объекты, но, похоже, это не работает, поскольку я получаю сообщение об ошибке, что test_mycoordinator не может быть разрешен. Я собираюсь протестировать этот класс неправильно? Есть ли питонический способ, которым я должен его протестировать?

Вот как выглядит мой тестовый класс:

import unittest
from mock import Mock
from mypackage import mycoordinator

class MyTest(unittest.TestCase):

    def setUpModule(self):
        # Create a mock producer
        producer_attributes = ['__init__', 'run', 'stop']
        mock_producer = Mock(name='Producer', spec=producer_attributes)

        # Create a mock consumer
        consumer_attributes = ['__init__', 'run', 'stop']
        data_out = [{u'dataObjectID': u'test1'},
                    {u'dataObjectID': u'test2'},
                    {u'dataObjectID': u'test3'}]
        mock_consumer = Mock(
            name='Consumer', spec=consumer_attributes, return_value=data_out)

        self.coor = mycoordinator.test_mycoordinator(mock_producer, mock_consumer)

    def test_send_data(self):
        # Create some data and send it to the producer
        count = 0
        while count < 3:
            count += 1
            testName = 'test' + str(count)
            self.coor.sendData(testName , None)

И вот класс, который я пытаюсь протестировать:

class MyCoordinator():
    def __init__(self):
        # Process Command Line Arguments using argparse  
        ...

        # Initialize the producer and the consumer
        self.myproducer = producer.Producer(self.servers,
                                            self.producer_topic_name)

        self.myconsumer = consumer.Consumer(self.servers,
                                            self.consumer_topic_name)

    # Constructor used for testing -- DOES NOT WORK
    @classmethod
    def test_mycoordinator(cls, mock_producer, mock_consumer):
        cls.myproducer = mock_producer
        cls.myconsumer = mock_consumer

    # Send the data to the producer
    def sendData(self, data, key):
        self.myproducer.run(data, key)

    # Receive data from the consumer
    def getData(self):
        data = self.myconsumer.run()
        return data

person jencoston    schedule 11.04.2017    source источник
comment
откуда в твоем setUpModule берется mycordinator?   -  person dm03514    schedule 11.04.2017
comment
@ dm03514 извините, у меня была опечатка, когда я менял имена. Я отредактировал вопрос, чтобы исправить оператор импорта.   -  person jencoston    schedule 11.04.2017


Ответы (1)


Нет необходимости предоставлять отдельный конструктор. Насмешки исправляют ваш код, заменяя объекты на имитации. Просто используйте mock.patch() decorator в методах тестирования. ; он будет передавать ссылки на сгенерированные фиктивные объекты.

И producer.Producer(), и consumer.Consumer() затем замещаются перед созданием экземпляра:

import mock

class MyTest(unittest.TestCase):
    @mock.patch('producer.Producer', autospec=True)
    @mock.patch('consumer.Consumer', autospec=True)
    def test_send_data(self, mock_consumer, mock_producer):
        # configure the consumer instance run method
        consumer_instance = mock_consumer.return_value
        consumer_instance.run.return_value = [
            {u'dataObjectID': u'test1'},
            {u'dataObjectID': u'test2'},
            {u'dataObjectID': u'test3'}]

        coor = MyCoordinator()
        # Create some data and send it to the producer
        for count in range(3):
            coor.sendData('test{}'.format(count) , None)

        # Now verify that the mocks have been called correctly
        mock_producer.assert_has_calls([
            mock.Call('test1', None),
            mock.Call('test2', None),
            mock.Call('test3', None)])

Таким образом, в момент вызова test_send_data код mock.patch() заменяет ссылку producer.Producer фиктивным объектом. Затем ваш класс MyCoordinator использует эти фиктивные объекты, а не настоящий код. вызов producer.Producer() возвращает новый фиктивный объект (тот же объект, на который ссылается mock_producer.return_value) и т. д.

Я сделал предположение, что producer и consumer — это имена модулей верхнего уровня. Если это не так, укажите полный путь импорта. Из документации mock.patch():

target должен быть строкой в ​​формате 'package.module.ClassName'. Цель импортируется, а указанный объект заменяется новым объектом, поэтому цель должна быть импортируема из среды, из которой вы вызываете patch(). Цель импортируется при выполнении декорированной функции, а не во время декорирования.

person Martijn Pieters    schedule 11.04.2017
comment
Как @mock.patch('consumer.Consumer', autospec=True) издевается над потребительским классом, который я написал? Точнее, как он узнает, над чем я хочу издеваться? Я вижу ошибку, когда пытаюсь запустить тест (ImportError: нет модуля с именем потребителя). Мой класс Consumer находится в файле Consumer.py, который находится в mypackage. Так нужно ли как-то указывать полный путь к классу Consumer в патче? - person jencoston; 11.04.2017
comment
@jencoston: вы не указали в своем коде, откуда взялось consumer; Я сделал предположение, что это название модуля. Если consumer находится внутри пакета, укажите полное имя импорта: mock.patch('package.consumer.Consumer'). mock.patch импортирует модуль и исправит имя (последняя часть пути). - person Martijn Pieters; 12.04.2017