Las herramientas de testing descubren pruebas siguiendo convenciones. Si un archivo, una clase o un método no tiene el nombre esperado, puede ocurrir que la prueba exista, pero no se ejecute.
En este tema veremos cómo nombrar archivos, clases y métodos de prueba para que unittest los encuentre de forma automática.
Crea un proyecto nuevo:
mkdir nombres-pruebas-demo
cd nombres-pruebas-demo
Trabajaremos con una estructura simple para concentrarnos en las convenciones de nombres.
Crea un archivo llamado calculadora.py:
def sumar(a, b):
return a + b
def restar(a, b):
return a - b
def multiplicar(a, b):
return a * b
def dividir(a, b):
if b == 0:
raise ValueError("No se puede dividir por cero")
return a / b
Este archivo tendrá pruebas correctas y ejemplos de nombres incorrectos para ver qué descubre unittest.
Por defecto, unittest descubre archivos cuyo nombre coincide con el patrón test*.py. Una convención muy usada es comenzar con test_:
test_calculadora.py
Estos nombres también serían descubiertos por el patrón por defecto:
testcalculadora.py
test_operaciones.py
test_pruebas_basicas.py
En este curso usaremos test_*.py porque es más legible.
Crea test_calculadora.py:
import unittest
from calculadora import dividir, multiplicar, restar, sumar
class TestCalculadora(unittest.TestCase):
def test_sumar_dos_numeros(self):
resultado = sumar(2, 3)
self.assertEqual(resultado, 5)
def test_restar_dos_numeros(self):
resultado = restar(10, 4)
self.assertEqual(resultado, 6)
El archivo comienza con test_, la clase comienza con Test y los métodos comienzan con test_.
En unittest, las clases de prueba normalmente heredan de unittest.TestCase y se nombran comenzando con Test:
class TestCalculadora(unittest.TestCase):
pass
El nombre de la clase debe explicar qué unidad o comportamiento agrupa. Por ejemplo:
class TestCalculadora(unittest.TestCase):
pass
class TestValidacionesDeUsuario(unittest.TestCase):
pass
class TestCarrito(unittest.TestCase):
pass
Los métodos que deben ejecutarse como pruebas tienen que comenzar con test_:
def test_multiplicar_dos_numeros(self):
resultado = multiplicar(3, 4)
self.assertEqual(resultado, 12)
Si el método se llama multiplicar_dos_numeros, sin test_, unittest no lo ejecutará automáticamente.
Completa test_calculadora.py agregando estas pruebas dentro de la clase:
def test_multiplicar_dos_numeros(self):
resultado = multiplicar(3, 4)
self.assertEqual(resultado, 12)
def test_dividir_dos_numeros(self):
resultado = dividir(10, 2)
self.assertEqual(resultado, 5)
def test_dividir_por_cero_lanza_error(self):
with self.assertRaises(ValueError):
dividir(10, 0)
Cada nombre describe el comportamiento que se está verificando.
Desde la carpeta del proyecto, ejecuta:
python -m unittest -v
La salida esperada será similar a:
test_dividir_dos_numeros ... ok
test_dividir_por_cero_lanza_error ... ok
test_multiplicar_dos_numeros ... ok
test_restar_dos_numeros ... ok
test_sumar_dos_numeros ... ok
----------------------------------------------------------------------
Ran 5 tests in 0.001s
OK
Ahora crea un archivo llamado prueba_calculadora.py:
import unittest
from calculadora import sumar
class TestNombreDeArchivo(unittest.TestCase):
def test_sumar_en_archivo_mal_nombrandado(self):
self.assertEqual(sumar(1, 1), 2)
El método está bien nombrado, pero el archivo no comienza con test. Por eso python -m unittest no lo descubrirá por defecto.
Podemos indicar un patrón distinto para descubrir ese archivo:
python -m unittest discover -p "prueba_*.py"
Esto funciona, pero no conviene depender de patrones raros sin necesidad. Usar test_*.py evita confusiones.
Agrega este método dentro de TestCalculadora:
def sumar_sin_prefijo_test(self):
resultado = sumar(5, 5)
self.assertEqual(resultado, 10)
Este método no se ejecutará automáticamente porque no empieza con test_. La prueba parece existir, pero no forma parte de la suite descubierta.
Este ejemplo tampoco será tratado como una clase de pruebas por unittest:
class TestSinHerencia:
def test_algo(self):
assert sumar(1, 2) == 3
Aunque el nombre empieza con Test, la clase no hereda de unittest.TestCase. Para este curso mantendremos siempre esa herencia.
Un buen nombre de prueba describe el comportamiento esperado. Compara estos dos nombres:
def test_1(self):
pass
def test_dividir_por_cero_lanza_error(self):
pass
El segundo nombre permite entender qué regla se está probando sin abrir todo el cuerpo del método.
Estos nombres son válidos, pero poco informativos:
def test_funciona(self):
pass
def test_calculo(self):
pass
def test_error(self):
pass
Conviene nombrar la condición y el resultado esperado: test_precio_negativo_lanza_error, test_usuario_mayor_de_edad_esta_activo o test_lista_vacia_devuelve_cero.
| Elemento | Convención recomendada | Ejemplo |
|---|---|---|
| Archivo de prueba | Comenzar con test_ |
test_calculadora.py |
| Clase de prueba | Comenzar con Test y heredar de unittest.TestCase |
class TestCalculadora(unittest.TestCase) |
| Método de prueba | Comenzar con test_ |
test_sumar_dos_numeros |
| Nombre descriptivo | Indicar condición y resultado esperado | test_dividir_por_cero_lanza_error |
El archivo test_calculadora.py debería quedar así:
import unittest
from calculadora import dividir, multiplicar, restar, sumar
class TestCalculadora(unittest.TestCase):
def test_sumar_dos_numeros(self):
resultado = sumar(2, 3)
self.assertEqual(resultado, 5)
def test_restar_dos_numeros(self):
resultado = restar(10, 4)
self.assertEqual(resultado, 6)
def test_multiplicar_dos_numeros(self):
resultado = multiplicar(3, 4)
self.assertEqual(resultado, 12)
def test_dividir_dos_numeros(self):
resultado = dividir(10, 2)
self.assertEqual(resultado, 5)
def test_dividir_por_cero_lanza_error(self):
with self.assertRaises(ValueError):
dividir(10, 0)
if __name__ == "__main__":
unittest.main()
Después de ejecutar la suite, mira la línea Ran ... tests. Si esperabas cinco pruebas y aparece una cantidad menor, probablemente algún nombre no respeta la convención.
Ran 5 tests in 0.001s
Esta línea es una señal rápida para detectar pruebas que no fueron descubiertas.
prueba_algo.py: no coincide con el patrón por defecto.test_ en métodos: el método no se ejecuta.unittest.TestCase: la clase no se comporta como una clase de pruebas de unittest.test_1: son válidos, pero no explican la intención.mkdir nombres-pruebas-demo
cd nombres-pruebas-demo
python -m unittest
python -m unittest -v
python -m unittest discover
python -m unittest discover -p "test_*.py"
python -m unittest discover -p "prueba_*.py"
test_*.py es una convención clara y frecuente.unittest.TestCase.test_.En este tema vimos las convenciones de nombres que permiten a unittest descubrir pruebas automáticamente. Archivos, clases y métodos bien nombrados hacen que la suite sea más confiable y más fácil de leer.
En el próximo tema instalaremos pytest y realizaremos una primera ejecución con ese framework de pruebas.