我如何模拟 python 构造函数的一部分只是为了测试?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43354242/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 22:55:43  来源:igfitidea点击:

How do I mock part of a python constructor just for testing?

pythonunit-testingmockingpython-mock

提问by jencoston

I am new to Python, so I apologize if this is a duplicate or overly simple question. I have written a coordinator class that calls two other classes that use the kafka-python library to send/read data from Kafka. I want to write a unit test for my coordinator class but I'm having trouble figuring out how to best to go about this. I was hoping that I could make an alternate constructor that I could pass my mocked objects into, but this doesn't seem to be working as I get an error that test_mycoordinator cannot be resolved. Am I going about testing this class the wrong way? Is there a pythonic way I should be testing it?

我是 Python 新手,所以如果这是一个重复或过于简单的问题,我深表歉意。我编写了一个协调器类,它调用另外两个使用 kafka-python 库从 Kafka 发送/读取数据的类。我想为我的协调器类编写一个单元测试,但我无法弄清楚如何最好地解决这个问题。我希望我可以创建一个替代构造函数,我可以将我的模拟对象传递给它,但这似乎不起作用,因为我收到了 test_mycoordinator 无法解决的错误。我会以错误的方式测试这个类吗?我应该测试它的pythonic方式吗?

Here is what my test class looks like so far:

这是我的测试类到目前为止的样子:

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)

And here is the class I am trying to test:

这是我要测试的课程:

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

回答by Martijn Pieters

There is no need to provide a separate constructor. Mocking patches your codeto replace objects with mocks. Just use the mock.patch()decoratoron your test methods; it'll pass in references to the generated mock objects.

无需提供单独的构造函数。模拟修补您的代码以用模拟替换对象。只需在您的测试方法上使用mock.patch()装饰器;它将传入对生成的模拟对象的引用。

Both producer.Producer()and consumer.Consumer()are then mocked out beforeyou create the instance:

双方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)])

So the moment test_send_datais called, the mock.patch()code replaces the producer.Producerreference with a mock object. Your MyCoordinatorclass then uses those mock objects rather than the real code. calling producer.Producer()returns a new mock object (the same object that mock_producer.return_valuereferences), etc.

所以在test_send_data调用的那一刻,mock.patch()代码producer.Producer用一个模拟对象替换了引用。MyCoordinator然后您的类使用这些模拟对象而不是真正的代码。调用producer.Producer()返回一个新的模拟对象(mock_producer.return_value引用的同一对象)等。

I've made the assumption that producerand consumerare top-level module names. If they are not, provide the full import path. From the mock.patch()documentation:

我已经假设producerconsumer是顶级模块名称。如果不是,请提供完整的导入路径。从mock.patch()文档:

targetshould be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch()from. The target is imported when the decorated function is executed, not at decoration time.

目标应该是形式为 的字符串'package.module.ClassName'。目标被导入并且指定的对象替换为新对象,因此目标必须可从您正在调用的环境中导入patch()。目标是在执行装饰函数时导入的,而不是在装饰时导入。