El módulo unittest, a veces referido como PyUnit, forma parte de una serie de frameworks conocidos como xUnit. Estas librerías se encuentran en la mayoría de lenguajes y son casi un estándard a la hora de programar pruebas unitarias.
A diferencia de doctest, unittest ofrece la posibilidad de crear las pruebas en el propio código implementando una clase llamada unittest.TestCase en la que se incluirá un kit o batería de pruebas.
Cada una de las pruebas puede devolver tres respuestas en función del resultado:
Vamos a crear una prueba unitaria muy sencilla para ver su funcionamiento en un script tests.py:
import unittest
class Pruebas(unittest.TestCase):
def test(self):
pass
if __name__ == "__main__":
unittest.main()
En este sencillo ejemplo podemos observar como heredamos de la clase unittest.TestCase para crear una batería de pruebas.
Cada método dentro de esta clase será una prueba, que en nuestro ejemplo no lanza ninguna excepción ni error, porlo que significa que los tests pasarán correctamente, y finalmente ejecutamos el método main() para ejecutar todas las baterías:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Como vemos se ha 1 realizado 1 test y el resultado a sido OK.
Si en lugar de pasar, invocamos una execepción AssertError...
import unittest
class Pruebas(unittest.TestCase):
def test(self):
raise AssertionError()
if __name__ == "__main__":
unittest.main()
Entonces el test fallaría:
F
======================================================================
FAIL: test (__main__.Pruebas)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Hector\Desktop\test.py", line 5, in test
raise AssertionError()
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
En el supuesto caso que dentro del test diera un error no asertivo, entonces tendríamos un Error:
import unittest
class Pruebas(unittest.TestCase):
def test(self):
1/0
if __name__ == "__main__":
unittest.main()
Entonces el test fallaría:
E
======================================================================
ERROR: test (__main__.Pruebas)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Hector\Desktop\test.py", line 5, in test
1/0
ZeroDivisionError: division by zero
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
Con lo que sabemos podríamos crear tests complejos sirviéndonos de condiciones y excepciones AssertionError, pero la clase TestCase nos provee de un montón de alternativas.
Vamos a hacer un repaso de las más comunes, recordad que siempre devolverán True o False dependiendo de si pasan o no el test:
Si os interesa profundizar os dejo el enlace oficial: https://docs.python.org/3/library/unittest.html
Vamos a hacer algunos ejemplos para practicar.
import unittest
def doblar(a): return a*2
def sumar(a,b): return a+b
def es_par(a): return 1 if a%2 == 0 else 0
class PruebasFunciones(unittest.TestCase):
def test_doblar(self):
self.assertEqual(doblar(10), 20)
self.assertEqual(doblar('Ab'), 'AbAb')
def test_sumar(self):
self.assertEqual(sumar(-15, 15), 0)
self.assertEqual(sumar('Ab' ,'cd'), 'Abcd')
def test_es_par(self):
self.assertEqual(es_par(11), False)
self.assertEqual(es_par(68), True)
if __name__ == '__main__':
unittest.main()
Resultado:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
import unittest
class PruebasMetodosCadenas(unittest.TestCase):
def test_upper(self):
self.assertEqual('hola'.upper(), 'HOLA')
def test_isupper(self):
self.assertTrue('HOLA'.isupper())
self.assertFalse('Hola'.isupper())
def test_split(self):
s = 'Hola mundo'
self.assertEqual(s.split(), ['Hola', 'mundo'])
if __name__ == '__main__':
unittest.main()
Resultado:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Lo último importante a comentar es que la clase TestCase incorpora dos métodos extras.
El primero es setUp() y sirve para preparar el contexto de las pruebas, por ejemplo para escribir unos valores de prueba en un fichero conectarse a un servidor o a una base de datos.
Luego tendríamos tearDown() para hacer lo propio con la limpieza, borrar el fichero, desconectarse del servidor o borrar las entradas de prueba de la base de datos.
Este proceso de preparar el contexto se conoce como test fixture o accesorios de prueba.
Sólo por poner un ejemplo supongamos que necesitamos contar con una lista de elementos para realizar una serie de pruebas:
import unittest
def doblar(a): return a*2
class PruebaTestFixture(unittest.TestCase):
def setUp(self):
print("Preparando el contexto")
self.numeros = [1, 2, 3, 4, 5]
def test(self):
print("Realizando una prueba")
r = [doblar(n) for n in self.numeros]
self.assertEqual(r, [2, 4, 6, 8, 10])
def tearDown(self):
print("Destruyendo el contexto")
del(self.numeros)
if __name__ == '__main__':
unittest.main()
Resultado de la prueba:
Preparando el contexto
.
Realizando una prueba
Destruyendo el contexto
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Y con esto finalizamos el tema.
Ahora ya sabemos cómo documentar nuestro código docstrings, generar la documentación con pydoc, introducir pruebas en las docstrings combinando doctest, y crear pruebas avanzadas con el módulo unittest.
Estamos a un paso de finalizar con el curso, sólo nos falta ver cómo distribuir nuestros módulos y programas.
¡Nos vemos en la próxima unidad!