2017-04-11 2 views
1

Je suis nouveau sur Python, alors je m'excuse si c'est une question en double ou trop simple. J'ai écrit une classe de coordinateur qui appelle deux autres classes qui utilisent la bibliothèque kafka-python pour envoyer/lire des données de Kafka. Je veux passer un test d'unité pour mon cours de coordinateur, mais j'ai de la difficulté à trouver la meilleure façon d'y parvenir. J'espérais que je pourrais faire un constructeur alternatif que je pourrais passer mes objets raillés, mais cela ne semble pas fonctionner car j'obtiens une erreur que test_mycoordinator ne peut pas être résolu. Est-ce que je vais tester cette classe dans le mauvais sens? Y a-t-il une façon pythonique de le tester?Comment se moquer d'une partie d'un constructeur python juste pour tester?

Voici ce que ma classe de test ressemble 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 je suis en train 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 
+0

d'où viennent les 'mycordinator' dans votre' setUpModule'? – dm03514

+0

@ dm03514 désolé, j'ai eu une faute de frappe quand je changeais les noms que j'ai édité la question pour corriger la déclaration d'importation – jencoston

Répondre

3

Il n'y a pas besoin de fournir un constructeur différent. Mocking patches votre code pour remplacer les objets avec des faux-semblants. Il suffit d'utiliser le mock.patch() decorator sur vos méthodes de test; il va passer dans les références aux objets simulés générés.

Les deux producer.Producer() et consumer.Consumer() sont ensuite émulés avant création de 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)]) 

donc le moment test_send_data est appelé, le code mock.patch() remplace la référence producer.Producer avec un objet fantaisie. Votre classe MyCoordinator utilise ensuite ces objets fantaisie plutôt que le code réel. appelant producer.Producer() retourne un nouvel objet fictif (le même objet que mock_producer.return_value références), etc.

J'ai fait l'hypothèse que producer et consumer sont les noms des modules de haut niveau. Si ce n'est pas le cas, fournissez le chemin d'importation complet. À partir de la documentation mock.patch():

target should 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.

+0

Comment @ mock.patch ('consumer.Consumer', autospec = True) mock la classe de consommation que j'ai écrit ? Plus précisément, comment sait-il ce que je veux moqué? Je vois une erreur lorsque j'essaie de lancer le test (ImportError: aucun module nommé consumer). Ma classe Consumer est dans consumer.py qui est dans mypackage. Alors dois-je spécifier le chemin complet de la classe Consumer dans le patch d'une manière ou d'une autre? – jencoston

+0

@jencoston: Vous n'avez pas inclus d'où provient 'consumer' dans votre code; J'ai fait l'hypothèse que c'était le nom du module. Si 'consumer' est dans un paquet, indiquez le nom complet de l'import:' mock.patch ('paquet.consumer.Consumer') '. 'mock.patch' va importer le module et patcher le nom (dernière partie du chemin). –