Cuando una suite crece, no siempre queremos ejecutar todas las pruebas de la misma forma. Algunas son rápidas, otras lentas, algunas dependen de integración y otras representan errores conocidos que todavía no fueron corregidos.
En este tema veremos cómo clasificar, omitir y seleccionar pruebas con pytest.
Crea un proyecto nuevo:
mkdir pytest-marcadores-demo
cd pytest-marcadores-demo
Si pytest no está instalado en el entorno activo:
python -m pip install pytest
Crea un archivo llamado operaciones.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 calcular_descuento(precio, porcentaje):
return precio - (precio * porcentaje / 100)
def exportar_reporte(datos):
if not datos:
return "sin datos"
return f"reporte con {len(datos)} registros"
Crea test_marcadores.py con algunas pruebas normales:
import pytest
from operaciones import calcular_descuento, dividir, exportar_reporte, sumar
def test_sumar():
assert sumar(2, 3) == 5
def test_dividir():
assert dividir(10, 2) == 5
def test_dividir_por_cero():
with pytest.raises(ValueError):
dividir(10, 0)
Hasta aquí, pytest ejecutará todas las pruebas encontradas.
Podemos usar @pytest.mark.slow para clasificar una prueba:
@pytest.mark.slow
def test_exportar_reporte_lento():
datos = [{"id": 1}, {"id": 2}, {"id": 3}]
assert exportar_reporte(datos) == "reporte con 3 registros"
El marcador no cambia el resultado de la prueba. Solo agrega una etiqueta para poder seleccionarla después.
También podemos definir otro marcador:
@pytest.mark.integration
def test_exportar_reporte_sin_datos():
assert exportar_reporte([]) == "sin datos"
El nombre del marcador debe expresar intención: slow, integration, db, api, smoke, etc.
Para ejecutar solo las pruebas lentas:
python -m pytest -m slow
Para ejecutar solo pruebas de integración:
python -m pytest -m integration
Para ejecutar todo excepto las pruebas lentas:
python -m pytest -m "not slow"
También se pueden combinar expresiones:
python -m pytest -m "not slow and not integration"
Si usas marcadores personalizados, conviene registrarlos. Crea pytest.ini:
[pytest]
markers =
slow: pruebas lentas que no siempre se ejecutan durante el desarrollo
integration: pruebas que revisan integración entre componentes
smoke: pruebas rápidas para verificar comportamiento esencial
Esto evita advertencias y documenta el significado de cada marcador.
skip indica que una prueba no debe ejecutarse:
@pytest.mark.skip(reason="Funcionalidad pendiente de implementación")
def test_generar_pdf():
assert False
La prueba aparece como omitida y no falla la suite.
skipif permite omitir una prueba solo si se cumple una condición:
import sys
@pytest.mark.skipif(sys.platform != "win32", reason="Solo aplica en Windows")
def test_ruta_windows():
assert "\\" in "C:\\proyectos\\demo"
Este tipo de marcador es útil para diferencias de sistema operativo, versiones o dependencias opcionales.
xfail marca una prueba que esperamos que falle por un error conocido:
@pytest.mark.xfail(reason="Redondeo pendiente de corregir")
def test_descuento_con_redondeo_pendiente():
assert calcular_descuento(100, 33.333) == 66.67
Si la prueba falla, la suite no se considera fallida. Pytest la muestra como xfailed.
skip: la prueba no se ejecuta.xfail: la prueba se ejecuta, pero se espera que falle.Usa skip cuando no tiene sentido ejecutar la prueba ahora. Usa xfail cuando quieres dejar documentado un fallo conocido.
La opción -k ejecuta pruebas cuyo nombre coincide con una expresión:
python -m pytest -k dividir
También puedes excluir nombres:
python -m pytest -k "dividir and not cero"
También puedes indicar el archivo y la función exacta:
python -m pytest test_marcadores.py::test_sumar
Esta forma es útil mientras corriges una prueba específica.
La opción -rs muestra razones de pruebas omitidas:
python -m pytest -rs
La opción -rx muestra detalles de xfail:
python -m pytest -rx
El archivo test_marcadores.py puede quedar así:
import sys
import pytest
from operaciones import calcular_descuento, dividir, exportar_reporte, sumar
def test_sumar():
assert sumar(2, 3) == 5
def test_dividir():
assert dividir(10, 2) == 5
def test_dividir_por_cero():
with pytest.raises(ValueError):
dividir(10, 0)
@pytest.mark.slow
def test_exportar_reporte_lento():
datos = [{"id": 1}, {"id": 2}, {"id": 3}]
assert exportar_reporte(datos) == "reporte con 3 registros"
@pytest.mark.integration
def test_exportar_reporte_sin_datos():
assert exportar_reporte([]) == "sin datos"
@pytest.mark.skip(reason="Funcionalidad pendiente de implementación")
def test_generar_pdf():
assert False
@pytest.mark.skipif(sys.platform != "win32", reason="Solo aplica en Windows")
def test_ruta_windows():
assert "\\" in "C:\\proyectos\\demo"
@pytest.mark.xfail(reason="Redondeo pendiente de corregir")
def test_descuento_con_redondeo_pendiente():
assert calcular_descuento(100, 33.333) == 66.67
Desde la raíz del proyecto, ejecuta:
python -m pytest
En Windows, la salida esperada será similar a:
collected 8 items
test_marcadores.py .....s.x [100%]
6 passed, 1 skipped, 1 xfailed in 0.05s
En otros sistemas, la prueba marcada con skipif también puede aparecer como omitida.
Si registraste los marcadores, puedes dejar fuera lentas e integración:
python -m pytest -m "not slow and not integration"
Esto es común durante el desarrollo diario.
xfail.-m y -k: -m filtra marcadores; -k filtra nombres.mkdir pytest-marcadores-demo
cd pytest-marcadores-demo
python -m pip install pytest
python -m pytest
python -m pytest -m slow
python -m pytest -m "not slow"
python -m pytest -k dividir
python -m pytest -rs
python -m pytest -rx
python -m pytest test_marcadores.py::test_sumar
-m selecciona por marcador.-k selecciona por nombre o expresión.skip omite una prueba.skipif omite según una condición.xfail documenta un fallo esperado.pytest.ini.En este tema aprendimos a organizar y seleccionar pruebas con marcadores, a omitir casos con skip y skipif, y a documentar fallos conocidos con xfail.
En el próximo tema veremos cómo medir cobertura con coverage.py y pytest-cov.