unittest unit test mock python unit-testing mocking

mock - Cómo utilizar correctamente el simulacro en Python con la configuración de unittest



pytest mock (4)

Comenzaré por responder a sus preguntas, y luego daré un ejemplo detallado de cómo interactúan patch() y setUp() .

  1. No creo que se vea bien, vea mi respuesta a la pregunta # 3 en esta lista para más detalles.
  2. Sí, la llamada real al parche parece que debería burlarse del objeto que desea.
  3. No, casi nunca quieres usar el decorador setUp() en setUp() . setUp() suerte, porque el objeto se crea en setUp() y nunca se crea durante el método de prueba.
  4. No conozco ninguna forma de hacer que un objeto simulado genere una excepción sin importar esa excepción en su archivo de caso de prueba.
  5. No veo ninguna necesidad de patch.object() aquí. Simplemente le permite parchear los atributos de un objeto en lugar de especificar el objetivo como una cadena.

Para ampliar mi respuesta a la pregunta # 3, el problema es que el decorador patch() solo se aplica mientras se ejecuta la función decorada. Tan pronto como setUp() vuelve, el parche se elimina. En su caso, eso funciona, pero apuesto a que confundiría a alguien al ver esta prueba. Si realmente solo desea que el parche ocurra durante setUp() , sugeriría usar la instrucción with para que sea obvio que el parche se eliminará.

El siguiente ejemplo tiene dos casos de prueba. TestPatchAsDecorator muestra que decorar la clase aplicará el parche durante el método de prueba, pero no durante setUp() . TestPatchInSetUp muestra cómo se puede aplicar el parche para que esté en su lugar durante la setUp() y el método de prueba. Al llamar a self.addCleanUp() se asegura de que el parche se quitará durante tearDown() .

import unittest from mock import patch @patch(''__builtin__.sum'', return_value=99) class TestPatchAsDecorator(unittest.TestCase): def setUp(self): s = sum([1, 2, 3]) self.assertEqual(6, s) def test_sum(self, mock_sum): s1 = sum([1, 2, 3]) mock_sum.return_value = 42 s2 = sum([1, 2, 3]) self.assertEqual(99, s1) self.assertEqual(42, s2) class TestPatchInSetUp(unittest.TestCase): def setUp(self): patcher = patch(''__builtin__.sum'', return_value=99) self.mock_sum = patcher.start() self.addCleanup(patcher.stop) s = sum([1, 2, 3]) self.assertEqual(99, s) def test_sum(self): s1 = sum([1, 2, 3]) self.mock_sum.return_value = 42 s2 = sum([1, 2, 3]) self.assertEqual(99, s1) self.assertEqual(42, s2)

En mi intento de aprender TDD, intentar aprender pruebas de unidad y usar simulacro con python. Lentamente aprendiendo, pero no estoy seguro de si estoy haciendo esto correctamente. Prevenido: estoy trabajando con python 2.4 porque las API del proveedor vienen como archivos de 2.4 pyc precompilados, así que estoy usando simulacro 0.8.0 y unittest (no unittest2)

Dado este código de ejemplo en ''mymodule.py''

import ldap class MyCustomException(Exception): pass class MyClass: def __init__(self, server, user, passwd): self.ldap = ldap.initialize(server) self.user = user self.passwd = passwd def connect(self): try: self.ldap.simple_bind_s(self.user, self.passwd) except ldap.INVALID_CREDENTIALS: # do some stuff raise MyCustomException

Ahora en mi archivo de caso de prueba ''test_myclass.py'', quiero burlarme del objeto ldap. ldap.initialize devuelve el ldap.ldapobject.SimpleLDAPObject, así que pensé que ese sería el método que tendría que burlarme.

import unittest from ldap import INVALID_CREDENTIALS from mock import patch, MagicMock from mymodule import MyClass class LDAPConnTests(unittest.TestCase): @patch(''ldap.initialize'') def setUp(self, mock_obj): self.ldapserver = MyClass(''myserver'',''myuser'',''mypass'') self.mocked_inst = mock_obj.return_value def testRaisesMyCustomException(self): self.mocked_inst.simple_bind_s = MagicMock() # set our side effect to the ldap exception to raise self.mocked_inst.simple_bind_s.side_effect = INVALID_CREDENTIALS self.assertRaises(mymodule.MyCustomException, self.ldapserver.connect) def testMyNextTestCase(self): # blah blah

Me lleva a un par de preguntas:

  1. ¿Se ve bien? :)
  2. ¿Es esa la forma correcta de intentar simular un objeto que se crea una instancia dentro de la clase que estoy probando?
  3. ¿Está bien llamar al decorador @patch en la configuración o esto va a causar efectos secundarios extraños?
  4. ¿Hay alguna forma de simular que se genere la excepción ldap.INVALID_CREDENTIALS sin tener que importar la excepción a mi archivo de prueba?
  5. ¿Debería estar usando patch.object () en su lugar y si es así, cómo?

Gracias.


Me gustaría señalar una variación de la respuesta aceptada en la que se pasa un new argumento al decorador patch() :

from unittest.mock import patch, Mock MockSomeClass = Mock() @patch(''mymodule.SomeClass'', new=MockSomeClass) class MyTest(TestCase): def test_one(self): # Do your test here

Tenga en cuenta que en este caso, ya no es necesario agregar el segundo argumento, MockSomeClass , a cada método de prueba, que puede ahorrar mucha repetición de código.

Puede encontrar una explicación de esto en https://docs.python.org/3/library/unittest.mock.html#patch :

Si se usa patch() como decorador y se omite el nuevo , el simulacro creado se pasa como un argumento adicional a la función decorada.

Las respuestas sobre todo omiten lo nuevo , pero puede ser conveniente incluirlo.


Puede usar patch() como decorador de clase, no solo como decorador de funciones. A continuación, puede pasar en la función simulada como antes:

@patch(''mymodule.SomeClass'') class MyTest(TestCase): def test_one(self, MockSomeClass): self.assertIs(mymodule.SomeClass, MockSomeClass)

Ver: 26.5.3.4. Aplicar el mismo parche a cada método de prueba (que también enumera alternativas)

Tiene más sentido configurar el parche de esta manera en la configuración si desea que se realicen los parches para todos los métodos de prueba.


Si tiene muchos parches para aplicar y desea que se apliquen a las cosas inicializadas en los métodos de configuración, intente esto:

def setUp(self): self.patches = { "sut.BaseTestRunner._acquire_slot": mock.Mock(), "sut.GetResource": mock.Mock(spec=GetResource), "sut.models": mock.Mock(spec=models), "sut.DbApi": make_db_api_mock() } self.applied_patches = [mock.patch(patch, data) for patch, data in self.patches.items()] [patch.apply for patch in self.applied_patches] . . rest of setup . def tearDown(self): patch.stopall()