2012-10-05 39 views
13

Tengo una clase Python TestCase donde todos los métodos de prueba, excepto uno, necesitan parchear un objeto de la misma manera. El otro método necesita algún otro comportamiento del mismo objeto. Estoy usando mock, así que lo hice:Anulación del decodificador de parches de Python Mock

@mock.patch('method_to_patch', mock.Mock(return_value=1)) 
class Tests(TestCase): 

    @mock.patch('method_to_patch', mock.Mock(return_value=2)) 
    def test_override(self): 
     (....) 

Pero eso no funciona. Cuando se ejecuta test_override, aún llama al comportamiento parchado del decorador de clases.

Después de una gran cantidad de depuración, descubrí que durante el TestSuite acumulación, la @patch alrededor test_override se está llamando antes de la una alrededor de Tests, y desde mock aplicar los parches en orden, el decorador de clase es reemplazar el método decorador.

¿Es correcto este pedido? Esperaba lo contrario y no estoy seguro de cómo anular el parche ... ¿Quizás con una declaración with?

+0

¿Es necesario que 'test_override' esté en' Tests'? Me puedo imaginar tener otro 'TestCase' que para' test_override' y pruebas similares. Si quieres que 'method_to_patch' haga una cosa diferente a' test_override', entonces también puedo imaginar que está justificado tener otra clase 'TestCase'. Eso también resolvería su problema y hace que el código sea mucho más legible para otros desarrolladores (en comparación con el "parcheo de parches"). –

+0

@SimeonVisser Mover 'test_override' a otra clase no es realmente una opción, debido a las reglas que usamos para estructurar las pruebas. Son todas las pruebas relativas a la misma vista, por lo que las ponemos en un solo 'TestCase' – Gabe

Respuesta

18

Bueno, resulta que una buena noche de sueño y una ducha fría me hicieron repensar todo el asunto. Todavía soy muy nuevo en el concepto de burlarse, por lo que todavía no se ha hundido del todo bien.

Lo que pasa es que no hay necesidad de anular el parche a un objeto burlado. Es un objeto burlado y eso significa que puedo hacer que haga cualquier cosa. Así que mi primer intento fue:

@mock.patch('method_to_patch', mock.Mock(return_value=1)) 
class Tests(TestCase): 

    def test_override(self): 
     method_to_patch.return_value = 2 
     (....) 

que funcionaba, pero tuvo el efecto secundario de cambiar el valor de retorno para todas las siguientes pruebas. Entonces probé:

@mock.patch('method_to_patch', mock.Mock(return_value=1)) 
class Tests(TestCase): 

    def test_override(self): 
     method_to_patch.return_value = 2 
     (....) 
     method_to_patch.return_value = 1 

Y funcionó a las mil maravillas. Pero parecía demasiado código. Entonces me fui al final de la carretera de gestión de contexto, así:

@mock.patch('method_to_patch', mock.Mock(return_value=1)) 
class Tests(TestCase): 

    def test_override(self): 
     with mock.patch('method_to_patch', mock.Mock(return_value=2): 
      (....) 

creo que parece más claro y conciso.

Sobre el orden en que se aplicaron los decoradores patch, en realidad es el orden correcto. Al igual que los decoradores apilados se aplican desde abajo hacia arriba, se supone que un decorador de métodos debe llamarse antes que el decorador de clases. Supongo que tiene sentido, solo esperaba el comportamiento contrario.

De todos modos, espero que esto ayude a una pobre alma novata como la mía en el futuro.