Test + Mocks

Sergio Orozco Torres

PSL

Cambios

Un inevitable compañero a la hora de desarrollar una aplicación

Un código de ejemplo


def hola(nombre):
    return 'hola %s' % nombre
          

>>>print(hola("mundo"))
hola mundo
          

def hola(nombre):
    return 'saludos %s' % nombre
					

>>>print(hola("mundo"))
saludos mundo
					

A ver si aún no importa


import os
def ruta_images(img):
    return os.path.join('img', img)
          

>>> print(ruta_images('imagen'))
img/imagen
          

import os
def ruta_images(img):
    return os.path.join('uploads','img', img)
					

>>> print(ruta_images('imagen'))
uploads/img/imagen
					

¿Y por que no me procesa la imagen que subí?

unittest

Es un set de utilitarios para verificar el comportamiento de una aplicación usando una serie de test cases

Assert

Verifica si el resultado que se espera es igual al resultado arrojado por la aplicación

Un test básico


class Original:
    def saludo(self, nombre):
        return 'saludos %s, soy original' % nombre
				

import unittest
from mi_paquete.mi_modulo import Original

class OriginalTestCase(unittest.TestCase):
    def test_saludo(self):
        original = Original()
        self.assertEqual('saludos Sergio, soy original',
				original.saludo('Sergio'))
				

$ python -m unittest discover -v
test_saludo (tests.test_mi_modulo.OriginalTestCase) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

import unittest
from mi_paquete.mi_modulo import Original

class OriginalTestCase(unittest.TestCase):
    def test_saludo(self):
        original = Original()
        self.assertEqual('saludos Sergio, no soy original',
				original.saludo('Sergio'))
				

test_saludo (tests.test_mi_modulo.OriginalTestCase) ... FAIL

======================================================================
FAIL: test_saludo (tests.test_mi_modulo.OriginalTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/.../ejemplo/tests/test_mi_modulo.py", line 8, in test_saludo
    original.saludo('Sergio'))
AssertionError: 'saludos Sergio, no soy original' != 'saludos Sergio, soy original'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
				

Mock

Es una implementación que simula el comportamiento de un objeto

Cuando usar mock

  • La función retorna valores no deterministicos
  • El estado de la función es dificil de reproducir
  • La ejecución de la función es lenta
  • El objeto no existe aún o su implementación puede cambiar
  • Debe incluir atributos para su prueba

Patch

Es una función o anotación que sirve para reemplazar un objeto por otro simulado

Se hace el reemplazo sobre el módulo que se esta testeando

Uso de patch


import unittest
from mock import patch, Mock
from mi_paquete.otro_modulo import saludo_foraneo

class SaludoOriginalTestCase(unittest.TestCase):
    def test_saludo(self):
        self.assertEqual('saludos Pythonist, soy original',
        saludo_foraneo())

    @patch('mi_paquete.otro_modulo.Original.saludo')
    def test_mock_saludo(self, mock_saludo):
        mock_saludo.return_value = 'cambie la salida'
        self.assertEqual('cambie la salida',
        saludo_foraneo())

				
			
test_saludo (tests.test_mi_modulo.OriginalTestCase) ... ok
test_mock_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok
test_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s
				

Sentinel

Nos permite verificar el valor de retorno de las funciones


@patch('mi_paquete.otro_modulo.Original.saludo')
def test_mock_sentinel_saludo(self, mock_saludo):
		mock_saludo.return_value = sentinel.salida
		self.assertEqual(sentinel.salida,
		saludo_foraneo())
		

Side effect

Nos permite especificar multiples valores de retorno o bien arrojar excepciones


@patch('mi_paquete.otro_modulo.Original.saludo')
def test_mock_raise_saludo(self, mock_saludo):
		mock_saludo.side_effect = ValueError('Lo hice reventar')
		self.assertRaises(ValueError)

@patch('mi_paquete.otro_modulo.Original.saludo')
def test_mock_multiple_saludo(self, mock_saludo):
		mock_saludo.side_effect = [sentinel.salida1, sentinel.salida2]
		self.assertEqual(sentinel.salida1,
		saludo_foraneo())
		self.assertEqual(sentinel.salida2,
		saludo_foraneo())
      

Al final


test_saludo (tests.test_mi_modulo.OriginalTestCase) ... ok
test_mock_call_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok
test_mock_multiple_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok
test_mock_raise_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok
test_mock_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok
test_mock_sentinel_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok
test_saludo (tests.test_otro_modulo.SaludoOriginalTestCase) ... ok

----------------------------------------------------------------------
Ran 7 tests in 0.002s

OK