интеграция макета и патча в модульный тест Python

У меня есть класс с несколькими методами, для которых я пишу модульные тесты. Для минимального воспроизводимого примера я прикрепляю 3 метода из этого класса:

Класс, методы которого я тестирую:

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]

Мой тестовый класс выглядит следующим образом:

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.')

Что я хочу:

Я хотел бы использовать mock/patch в своем тестовом классе, где это возможно. Это, вероятно, означает, что MockGuard, MockSession и MockRequest будут заменены экземплярами mock. Я хотел бы посмотреть, как это можно улучшить, чтобы использовать mock/patch из пакета unittest в python.


person S Khurana    schedule 19.08.2020    source источник


Ответы (1)


Хорошо, пытаюсь дать вам представление. В тестах вы создали поддельный метод addCookie для ваших тестов, но вы используете его только для проверки того, как был вызван addCookie. Так, например, ваши тесты 3 и 4 вы могли бы переписать:

   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

В других тестах вам также может понадобиться проверить аргументы, используемые в вызовах. Вам всегда нужно определить, что вы на самом деле хотите протестировать, а затем настроить свои моки так, чтобы тестировалась только эта функциональность.

Также обратите внимание, что в некоторых случаях может иметь смысл использовать дополнительные фиктивные классы, как вы сделали — в этом нет ничего плохого, если это лучше всего подходит для вас.

person MrBean Bremen    schedule 19.08.2020
comment
Большое спасибо, что поделились. Но то, что я пытаюсь проверить, иногда включает проверку значений, которые у меня есть в сеансе, а также файлы cookie. Например, я только что обновил свой вопрос с помощью метода set_nonce, renew_session_id — тестовый пример test_03_set_nonce, test_02_renew_session_id также проверяют значения в сеансе. Как бы я справился с этим? @MrBran Бремен - person S Khurana; 19.08.2020
comment
Может не стоит? Как я уже писал, нет ничего плохого в использовании настраиваемых фиктивных объектов, если это соответствует вашим потребностям. Так как вы используете их только как аргументы, то и патчить ничего не нужно. Итак, мой вопрос: что не так с вашими тестами? - person MrBean Bremen; 19.08.2020
comment
Я считаю, что невозможность издеваться над этими объектами привела бы к написанию большого количества кода в самом тестовом классе, поэтому в идеале я хотел бы издеваться/исправлять вещи, которые я могу. Я понимаю, что созданные вручную mock_objects работают для меня здесь, но я также пытаюсь понять, могу ли я действительно использовать mock из пакета для уточнения определенных тестовых случаев. Я могу решить придерживаться того, что у меня есть, но я пытаюсь увидеть свои варианты с макетом и проверить, выиграю ли я от этого, улучшит ли он мой код и подходит ли он для тестовых случаев. Кроме того, я считаю, что это поможет мне понять правильные варианты его использования в будущем. - person S Khurana; 19.08.2020
comment
и точно ли фиктивные экземпляры нельзя использовать для проверки/тестирования каких-либо изменений в значениях? Например: издевательство над объектом запроса с использованием request = mock.Mock() и добавление значения в request.cookies = {'hello':'test'} не будет работать для тестирования? - person S Khurana; 19.08.2020
comment
Моки собирают только аргументы вызова. Пока вам достаточно знать вызовы и аргументы вызова, это будет работать, в противном случае вам придется делать больше (например, написать свой собственный макет сборщика, как вы это сделали). - person MrBean Bremen; 19.08.2020