intégration de maquette et de patch dans un test unitaire python

Aug 20 2020

J'ai une classe avec quelques méthodes pour lesquelles j'écris des cas de test unitaires. Pour un exemple minimum reproductible, j'attache 3 des méthodes de cette classe:

Classe dont je teste les méthodes de:

class WebViewLincSession(object):
    def renew_session_id(self, request):
            session = request.getSession()
            new_session_key = self.get_token()
            while new_session_key in session.guard.sessions:  # just in case the key is already used
                new_session_key = self.get_token()
            session.guard.sessions.pop(session.uid)  # remove the current session
            session.uid = new_session_key  # update the key
            session.guard.sessions[new_session_key] = session  # add session back with the new key
            request.addCookie(session.guard.cookieKey, new_session_key, path='/', secure=True, httpOnly=True)  # send updated cookie value
    def set_nonce(self, request):
        '''
        create a nonce value and send it as cookie
        '''
        if self._nonce_key is None:
            if self._NONCE_FOR_TEST:
                self._nonce_key = 'ecnon_for_test'
            else:
                self._nonce_key = 'ecnon_' + self.get_token()
            
        new_nonce_value = self.get_token()
        while new_nonce_value in self._nonce:  # just in case the value is already used
            new_nonce_value = self.get_token()
        
        now = time()
        stay_alive = now + self._STAY_ALIVE

        # reset timeout value for all existing nonces         
        for key in self._nonce.keys():
            if self._nonce[key] > stay_alive:
                self._nonce[key] = stay_alive
        
        self._nonce[new_nonce_value] = now + self._NONCE_TIMEOUT
        
        request.addCookie(self._nonce_key, new_nonce_value, path='/', secure=True, httpOnly=True)  # send updated cookie value
        
        return new_nonce_value


    def get_valid_nonce(self):
        now = time()
        return [nonce for nonce in self._nonce.keys() if self._nonce[nonce] > now]

Ma classe de test ressemble à ceci:

from __future__ import (division, absolute_import, with_statement)

from time import sleep

from mock import patch, MagicMock, mock, Mock
from requests.sessions import Session
from twisted.trial.unittest import TestCase

from viewlinc.webserver.web_viewlinc_session import WebViewLincSession


class MockGuard(object):
    '''Mock guard object for testing'''
    def __init__(self, *ags, **kwargs):
        ''' class constructor
        '''
        super(MockGuard, self).__init__(*ags, **kwargs)
        self.cookieKey = 'test_cookie_key'
        self.sessions = {'_test_session_': {}}

class MockSession(object):
    '''Mock session object for testing'''
    def __init__(self, *ags, **kwargs):
        ''' class constructor
        '''
        super(MockSession, self).__init__(*ags, **kwargs)
        self.guard = MockGuard()
        self.uid = '_test_session_'

class MockRequest(object):
    '''Mock Request object for testing'''
    def __init__(self, *ags, **kwargs):
        ''' class constructor
        '''
        super(MockRequest, self).__init__(*ags, **kwargs)
        self.session = MockSession()
        self.cookies = {}
  
    def getSession(self):
        ''' returns session object
        '''
        return self.session
          
    def addCookie(self, key, value, path='/', secure=True, httpOnly=True, expires=None):
        ''' add/replace cookie
        '''
        self.cookies[key] = {
            'value': value,
            'path': path,
            'secure': secure,
            'httpOnly': httpOnly,
            'expires': expires
        }
          
    def getCookie(self, key):
        ''' retrieve a cookie
        '''
        cookie = self.cookies.get(key, {'value': None})
        return cookie['value']

class WebViewLincSessionTests(TestCase):
    '''Test WebViewLincSession methods'''

    def __init__(self, *ags, **kwargs):
        ''' class constructor
        '''
        super(WebViewLincSessionTests, self).__init__(*ags, **kwargs)
        self.request = MockRequest()
        self.web_session = WebViewLincSession()
    


    def test_02_renew_session_id(self):
        '''Test renew_session_id
        '''
        self.web_session.renew_session_id(self.request)
        session = self.request.session
        return self.assertTrue(session.uid != '_test_session_' and session.uid in session.guard.sessions, 'renew_session_id failed')

    def test_03_set_nonce(self):
        '''Test set_nonce
        '''
        self.web_session.set_nonce(self.request)
        
        return self.assertTrue(len(self.request.cookies) > 0, 'set_nonce failed.')


    def test_04_get_valid_nonce(self):
        '''Test get_valid_nonce
        '''
        # use a clean session
        web_session = WebViewLincSession()
        web_session.set_nonce(self.request)
        web_session.set_nonce(self.request)
        valid_nonce = web_session.get_valid_nonce()

        self.assertTrue(len(valid_nonce) == 2, 'Expecting 2 valid nonces.')
        
        sleep(16)
        valid_nonce = web_session.get_valid_nonce()
        
        return self.assertTrue(len(valid_nonce) == 1, 'Expecting 1 valid nonce.')

Ce que je veux:

Je voudrais utiliser mock / patch dans ma classe de test partout où cela est possible. Cela signifie probablement que MockGuard, MockSessionet MockRequestêtre remplacé par des instances de simulation. Je voudrais voir comment cela peut être affiné pour utiliser un mock / patch à partir d'un unittestpackage en python.

Réponses

1 MrBeanBremen Aug 20 2020 at 02:57

Ok, j'essaye de te donner une idée. Dans les tests, vous avez créé une fausse addCookieméthode pour vos tests, mais vous ne l'utilisez que pour vérifier comment addCookiea été appelée. Ainsi, par exemple, vos tests 3 et 4 vous pourriez réécrire:

   def test_03_set_nonce(self):
        request = mock.Mock()
        self.web_session.set_nonce(request)
        # we only need to know that it was called once
        request.addCookie.assert_called_once()

   def test_04_get_valid_nonce(self):
        request = mock.Mock()
        web_session = WebViewLincSession()
        web_session.set_nonce(request)
        web_session.set_nonce(request)
        # check that addCookie it has been called twice
        self.assertEqual(2, request.addCookie.call_count)
        
        valid_nonce = web_session.get_valid_nonce()
        ... # the rest is not dependent on mocks

Dans d'autres tests, vous devrez peut-être également vérifier les arguments utilisés dans les appels. Vous devez toujours définir ce que vous voulez réellement tester, puis configurer vos simulacres de sorte que seule cette fonctionnalité soit testée.

Notez également que dans certains cas, il peut être judicieux d'utiliser des classes factices supplémentaires comme vous l'avez fait - il n'y a rien de mal à cela, si cela fonctionne le mieux pour vous.