web-dev-qa-db-ja.com

テストのためにpythonコンストラクタの一部をモックするにはどうすればよいですか?

私はPythonが初めてなので、これが重複した質問であるか、過度に単純な質問である場合は謝罪します。私は、kafka-pythonライブラリを使用してKafkaからデータを送受信する2つの他のクラスを呼び出すコーディネータークラスを作成しました。コーディネータークラスの単体テストを作成したいのですが、これをどのように行うのが最善かを考えるのに苦労しています。私は、モックされたオブジェクトを渡すことができる代替コンストラクタを作成できることを望んでいましたが、test_mycoordinatorを解決できないというエラーが発生するため、これは機能していないようです。このクラスを間違った方法でテストしようとしていますか? Pythonでテストする方法はありますか?

これまでのところ、私のテストクラスは次のようになります。

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
14
jencoston

別のコンストラクターを提供する必要はありません。モックコードをパッチオブジェクトをモックに置き換えます。テストメソッドで mock.patch() decorator を使用するだけです。生成されたモックオブジェクトへの参照を渡します。

producer.Producer()consumer.Consumer()の両方がモックアウトされますbeforeインスタンスを作成します:

_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_が参照するオブジェクト)などを返します。

producerconsumerはトップレベルのモジュール名であると仮定しました。そうでない場合は、完全なインポートパスを指定します。 mock.patch()ドキュメントから:

targetは_'package.module.ClassName'_という形式の文字列でなければなりません。ターゲットがインポートされ、指定されたオブジェクトが新しいオブジェクトに置き換えられるため、patch()を呼び出している環境からターゲットをインポートできる必要があります。ターゲットは、装飾時ではなく、装飾された関数が実行されたときにインポートされます。

21
Martijn Pieters