2 votes

Le constructeur de l'objet simulé est toujours appelé ?

Je suis vraiment novice en matière de Python et de tests unitaires et j'essaie d'écrire des tests unitaires pour un gestionnaire en ce moment.

Une version simplifiée du gestionnaire ressemble à ceci :

some_handler.py

def handleEvent(event): 
   user = event....
   action = event...
   response = getIsAuthorized(user, action)
   return convertToHttpResponse(response)

getIsAuthorized(user, action):
   ...
   authorizer = SomeAuthorizer()
   return authorizer.isAuthorized(user, action)

SomeAuthorizer est une classe simple :

some_authorizer.py

class SomeAuthorizer(object):
   __init__(self):
      # some instantiation of stuff needed for the 3P client
      ... 
      # creation of some 3P auth client
      self.client = Some3PClient(...)

   is_authorized(user, action): 
      return self.client.is_authorized(user, action)

Le code lui-même fonctionne comme prévu, mais mes tests me donnent du fil à retordre. J'ai réussi à le faire fonctionner dans certaines circonstances avec :

    @patch.object(SomeAuthorizer, 'is_authorized')
    def test_authorization_request_not_authorized(self, mock_is_authorized):
        mock_is_authorized.return_value = False

        response = handle_request(test_event("test_user", "testing"))
        assert response == status_code(200, False)

Mais ensuite, lorsqu'un autre développeur a exécuté les tests sur sa machine, il a échoué et il semblait que la raison était qu'il n'avait pas configuré localement certains éléments d'environnement dont le constructeur de l'Authorizer avait besoin (les erreurs qu'ils ont obtenues localement étaient des problèmes de création d'une instance de SomeAuthorizer lorsque les tests étaient exécutés). Mais si j'ai bien compris, le constructeur ne devrait même pas être appelé parce qu'il est simulé ? Est-ce que ce n'est pas le cas ?

Des conseils sur la façon de contourner ce problème ? Je me frappe la tête contre le mur en essayant de comprendre comment les mocks fonctionnent et quelle combinaison de patch / mock fonctionnerait mais ça ne va pas loin.

2voto

Voici une solution fonctionnelle que vous pouvez utiliser comme référence. Il s'agit de se moquer de l'ensemble du SomeAuthorizer Ainsi, sa classe __init__() ne serait pas exécuté, ni aucune de ses méthodes comme is_authorized() .

Arbre de fichiers

$ tree
.
 some_authorizer.py
 some_handler.py
 test_auth.py

some_authorizer.py

class Some3PClient:
    def is_authorized(self, user, action):
        return True  # Real implementation returns True

class SomeAuthorizer(object):
   def __init__(self):
      print("Real class __init__ called")
      self.client = Some3PClient()

   def is_authorized(self, user, action):
      print("Real class is_authorized called")
      return self.client.is_authorized(user, action)

some_handler.py

from some_authorizer import SomeAuthorizer  # The module we want to mock. To emphasize, the version we need to mock is "some_handler.SomeAuthorizer" and not "some_authorizer.SomeAuthorizer".

def handleEvent(event):
   user = "event...."
   action = "event..."
   response = getIsAuthorized(user, action)
   return convertToHttpResponse(response)

def getIsAuthorized(user, action):
   authorizer = SomeAuthorizer()
   return authorizer.is_authorized(user, action)

def convertToHttpResponse(response):
    return (200, response)

test_auth.py

from unittest.mock import patch

from some_handler import handleEvent

def test_real_class():
    response = handleEvent("test_event")
    assert response == (200, True)
    print(response)

@patch("some_handler.SomeAuthorizer")  # As commented in some_handler.py, patch the full path "some_handler.SomeAuthorizer" and not "SomeAuthorizer" nor "some_authorizer.SomeAuthorizer". If you want to patch the latter for all that uses it, you have to reload the modules that imports it.
def test_mock_class(mock_some_authorizer):
    mock_some_authorizer.return_value.is_authorized.return_value = False  # Mock implementation returns False

    response = handleEvent("test_event")
    assert response == (200, False)
    print(response)

Sortie

$ pytest -q -rP
============================================================================================ PASSES ============================================================================================
_______________________________________________________________________________________ test_real_class ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------
Real class __init__ called
Real class is_authorized called
(200, True)
_______________________________________________________________________________________ test_mock_class ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------
(200, False)
2 passed in 0.05s

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X