Integration von Mock und Patch in einen Python-Unit-Test
Ich habe eine Klasse mit einigen Methoden, für die ich Unit-Testfälle schreibe. Als minimales reproduzierbares Beispiel füge ich 3 der Methoden aus dieser Klasse hinzu:
Klasse, die ich teste Methoden von:
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]
Meine Testklasse sieht folgendermaßen aus:
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.')
Was ich will:
Ich möchte Mock / Patch in meiner Testklasse verwenden, wo immer dies möglich ist. Das bedeutet wahrscheinlich, dass MockGuard, MockSessionund MockRequestmit Instanzen von Mock ersetzt werden. Ich würde gerne sehen, wie dies verfeinert werden kann, um Mock / Patch aus dem unittestPaket in Python zu verwenden.
Antworten
Ok, ich versuche dir eine Idee zu geben. In den Tests haben Sie eine gefälschte addCookieMethode für Ihre Tests erstellt, aber Sie verwenden sie nur, um zu überprüfen, wie addCookieaufgerufen wurde. So könnten Sie beispielsweise Ihre Tests 3 und 4 neu schreiben:
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
In anderen Tests müssen Sie möglicherweise auch die in den Aufrufen verwendeten Argumente überprüfen. Sie müssen immer definieren, was Sie tatsächlich testen möchten, und dann Ihre Mocks so einrichten, dass nur diese Funktionalität getestet wird.
Beachten Sie auch, dass es in einigen Fällen sinnvoll sein kann, zusätzliche Scheinklassen zu verwenden, wie Sie es getan haben - daran ist nichts auszusetzen, wenn dies für Sie am besten funktioniert.