5. Ejecutar pruebas con python -m unittest

5.1 Objetivo del tema

En el tema anterior escribimos las primeras pruebas con unittest. Ahora nos concentraremos en la ejecución: cómo correr todas las pruebas, una carpeta, un archivo, una clase o un método específico.

Esto es importante porque en un proyecto real no siempre queremos ejecutar todo. A veces necesitamos correr una sola prueba mientras corregimos un error, y luego ejecutar la suite completa antes de terminar.

Comando principal del tema: python -m unittest

5.2 Por qué usamos python -m unittest

El comando python -m unittest ejecuta el módulo unittest usando el mismo intérprete de Python que está activo en la terminal.

Esto evita confusiones cuando hay varias versiones de Python instaladas. También funciona sin instalar paquetes externos, porque unittest forma parte de la biblioteca estándar.

5.3 Crear una carpeta de práctica

Crea un proyecto nuevo para este tema:

mkdir ejecutar-unittest-demo
cd ejecutar-unittest-demo

Crearemos una estructura con código principal y pruebas separadas.

5.4 Crear la estructura del proyecto

Crea dos carpetas:

mkdir app
mkdir tests

Agrega archivos __init__.py para trabajar con paquetes. En Windows PowerShell:

New-Item app\__init__.py -ItemType File
New-Item tests\__init__.py -ItemType File

En Linux o macOS:

touch app/__init__.py
touch tests/__init__.py

5.5 Crear el código de ejemplo

Dentro de app, crea numeros.py:

def sumar(a, b):
    return a + b


def dividir(a, b):
    if b == 0:
        raise ValueError("No se puede dividir por cero")
    return a / b


def es_mayor_de_edad(edad):
    return edad >= 18

Este archivo tiene funciones simples para practicar distintas formas de ejecución.

5.6 Crear el primer archivo de pruebas

Dentro de tests, crea test_numeros.py:

import unittest

from app.numeros import dividir, es_mayor_de_edad, sumar


class TestNumeros(unittest.TestCase):

    def test_sumar_dos_numeros(self):
        self.assertEqual(sumar(2, 3), 5)

    def test_dividir_dos_numeros(self):
        self.assertEqual(dividir(10, 2), 5)

    def test_dividir_por_cero_lanza_error(self):
        with self.assertRaises(ValueError):
            dividir(10, 0)

    def test_edad_18_es_mayor_de_edad(self):
        self.assertTrue(es_mayor_de_edad(18))

5.7 Crear un segundo archivo de pruebas

Dentro de app, crea textos.py:

def normalizar(texto):
    return texto.strip().lower()


def contar_palabras(texto):
    texto = texto.strip()
    if not texto:
        return 0
    return len(texto.split())

Luego crea tests/test_textos.py:

import unittest

from app.textos import contar_palabras, normalizar


class TestTextos(unittest.TestCase):

    def test_normalizar_texto(self):
        self.assertEqual(normalizar("  Hola  "), "hola")

    def test_contar_palabras(self):
        self.assertEqual(contar_palabras("uno dos tres"), 3)

    def test_contar_palabras_en_texto_vacio(self):
        self.assertEqual(contar_palabras("   "), 0)

5.8 Estructura del proyecto

La estructura queda así:

ejecutar-unittest-demo/
|-- app/
|   |-- __init__.py
|   |-- numeros.py
|   `-- textos.py
`-- tests/
    |-- __init__.py
    |-- test_numeros.py
    `-- test_textos.py

5.9 Ejecutar todas las pruebas

Desde la raíz del proyecto, ejecuta:

python -m unittest

Salida esperada:

Salida esperada al ejecutar todas las pruebas con unittest
.......
----------------------------------------------------------------------
Ran 7 tests in 0.001s

OK

Este comando descubre pruebas en archivos cuyo nombre coincide con el patrón esperado por unittest.

5.10 Ejecutar con discover

La forma explícita de pedir descubrimiento automático es:

python -m unittest discover

Este comando busca pruebas desde la carpeta actual. En proyectos simples suele producir el mismo resultado que python -m unittest.

5.11 Ejecutar pruebas de una carpeta

Para indicar la carpeta donde están las pruebas:

python -m unittest discover -s tests

La opción -s significa start directory, es decir, carpeta desde donde comienza la búsqueda.

5.12 Usar un patrón de archivos

Por defecto, unittest busca archivos con patrón test*.py. Podemos indicarlo explícitamente:

python -m unittest discover -s tests -p "test_*.py"

La opción -p define el patrón de nombres de archivo. En este curso usaremos archivos que comienzan con test_.

5.13 Ejecutar un archivo de pruebas

Para correr solo las pruebas de números:

python -m unittest tests.test_numeros

Observa que usamos puntos en lugar de barras y no escribimos la extensión .py.

5.14 Ejecutar una clase

Para ejecutar una clase específica:

python -m unittest tests.test_numeros.TestNumeros

El formato es paquete.archivo.Clase.

5.15 Ejecutar un método de prueba

Para correr una sola prueba:

python -m unittest tests.test_numeros.TestNumeros.test_dividir_por_cero_lanza_error

Este formato es muy útil mientras corregimos un comportamiento puntual.

5.16 Ejecutar con salida detallada

La opción -v muestra el nombre de cada prueba:

python -m unittest -v

También puede combinarse con ejecución selectiva:

python -m unittest tests.test_textos -v

5.17 Ejecutar y detenerse ante la primera falla

La opción -f detiene la ejecución cuando ocurre la primera falla o error:

python -m unittest -f

Puede ser útil cuando la suite es grande y queremos corregir un problema por vez.

5.18 Ejecutar sin mostrar salida capturada

La opción -b captura la salida estándar durante las pruebas. Si una prueba pasa, no muestra los print. Si falla, muestra la salida relacionada:

python -m unittest -b

Esto ayuda a mantener limpia la terminal cuando hay mensajes impresos durante las pruebas.

5.19 Usar varias opciones juntas

Podemos combinar opciones:

python -m unittest discover -s tests -p "test_*.py" -v

Este comando busca pruebas en tests, usa el patrón test_*.py y muestra salida detallada.

5.20 Qué ocurre si no encuentra pruebas

Si unittest no encuentra pruebas, puede mostrar:

Ran 0 tests in 0.000s

OK

Esto no significa que el proyecto esté bien probado. Significa que no encontró pruebas para ejecutar. Revisa nombres de archivos, nombres de métodos, carpeta actual y comando usado.

5.21 Nombres correctos para descubrimiento

Para evitar problemas, usa estas convenciones:

  • Archivos de prueba: test_algo.py.
  • Clases de prueba: nombres que empiezan con Test.
  • Métodos de prueba: nombres que empiezan con test_.
  • Ejecutar comandos desde la raíz del proyecto.
  • Usar paquetes importables cuando se trabaja con carpetas.

5.22 Errores frecuentes

  • Usar barras en el nombre del módulo: escribe tests.test_numeros, no tests/test_numeros.py, cuando ejecutas por módulo.
  • Olvidar __init__.py: puede causar problemas al importar módulos como paquetes.
  • Ejecutar desde una carpeta incorrecta: Python puede no encontrar app o tests.
  • Nombrar métodos sin test_: no serán descubiertos como pruebas.
  • Confundir prueba no encontrada con prueba exitosa: Ran 0 tests es una señal de problema.

5.23 Comandos usados en este tema

python -m unittest
python -m unittest discover
python -m unittest discover -s tests
python -m unittest discover -s tests -p "test_*.py"
python -m unittest tests.test_numeros
python -m unittest tests.test_numeros.TestNumeros
python -m unittest tests.test_numeros.TestNumeros.test_dividir_por_cero_lanza_error
python -m unittest -v
python -m unittest tests.test_textos -v
python -m unittest -f
python -m unittest -b
python -m unittest discover -s tests -p "test_*.py" -v

5.24 Qué debes recordar de este tema

  • python -m unittest permite ejecutar pruebas sin instalar herramientas externas.
  • discover busca pruebas automáticamente.
  • -s indica la carpeta inicial de búsqueda.
  • -p define el patrón de archivos de prueba.
  • Podemos ejecutar una suite completa, un archivo, una clase o un método.
  • Ran 0 tests no es una buena señal: significa que no se descubrieron pruebas.

5.25 Conclusión

En este tema aprendimos a ejecutar pruebas con python -m unittest de varias formas. Esto permite trabajar con precisión: correr una prueba específica mientras desarrollamos y ejecutar toda la suite antes de cerrar un cambio.

En el próximo tema estudiaremos las aserciones principales de unittest, que son las herramientas que usamos para expresar qué resultado esperamos en cada prueba.