web-dev-qa-db-fra.com

Comment se moquer d'une partie d'un constructeur python juste pour le test?

Je suis nouveau sur Python, donc je m'excuse s'il s'agit d'une question en double ou trop simple. J'ai écrit une classe coordinatrice qui appelle deux autres classes qui utilisent la bibliothèque kafka-python pour envoyer/lire des données depuis Kafka. Je veux écrire un test unitaire pour ma classe de coordinateur, mais j'ai du mal à trouver la meilleure façon de procéder. J'espérais pouvoir créer un autre constructeur dans lequel je pourrais passer mes objets simulés, mais cela ne semble pas fonctionner car j'obtiens une erreur dans laquelle test_mycoordinator ne peut pas être résolu. Vais-je tester cette classe dans le mauvais sens? Existe-t-il un moyen Pythonique de le tester?

Voici à quoi ressemble ma classe de test jusqu'à présent:

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)

Et voici la classe que j'essaie de tester:

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

Il n'est pas nécessaire de fournir un constructeur distinct. Mocking corrige votre code pour remplacer les objets par des mocks. Utilisez simplement le mock.patch() décorateur sur vos méthodes de test; il transmettra des références aux faux objets générés.

producer.Producer() et consumer.Consumer() sont ensuite simulés avant de créer l'instance:

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

Ainsi, au moment où test_send_data Est appelé, le code mock.patch() remplace la référence producer.Producer Par un objet factice. Votre classe MyCoordinator utilise alors ces objets fictifs plutôt que le vrai code. l'appel de producer.Producer() renvoie un nouvel objet simulé (le même objet auquel mock_producer.return_value fait référence), etc.

J'ai fait l'hypothèse que producer et consumer sont des noms de module de niveau supérieur. Si ce n'est pas le cas, indiquez le chemin d'importation complet. Dans la documentation mock.patch():

target doit être une chaîne sous la forme 'package.module.ClassName'. La cible est importée et l'objet spécifié remplacé par le nouvel objet, de sorte que la cible doit être importable à partir de l'environnement à partir duquel vous appelez patch(). La cible est importée lorsque la fonction décorée est exécutée, pas au moment de la décoration.

21
Martijn Pieters