29. Marcadores, skip, xfail y selección de pruebas

29.1 Objetivo del tema

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.

Idea clave: los marcadores ayudan a organizar la suite y ejecutar solo el grupo de pruebas que necesitas en cada momento.

29.2 Crear una carpeta de práctica

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

29.3 Crear un módulo simple

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"

29.4 Pruebas sin marcadores

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.

29.5 Marcar una prueba como lenta

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.

29.6 Marcar una prueba de integración

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.

29.7 Ejecutar solo pruebas con un marcador

Para ejecutar solo las pruebas lentas:

python -m pytest -m slow

Para ejecutar solo pruebas de integración:

python -m pytest -m integration

29.8 Excluir pruebas con un marcador

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"

29.9 Registrar marcadores en pytest.ini

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.

29.10 Usar skip para omitir una prueba

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.

29.11 Usar skipif para omitir según una condición

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.

29.12 Usar xfail para fallos esperados

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.

29.13 Diferencia entre skip y xfail

  • 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.

29.14 Seleccionar por nombre con -k

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"

29.15 Ejecutar una prueba concreta

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.

29.16 Mostrar razones de skip y xfail

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

29.17 Archivo completo de pruebas

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

29.18 Ejecutar toda la suite

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.

29.19 Ejecutar solo pruebas rápidas

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.

29.20 Errores frecuentes

  • No registrar marcadores: pytest mostrará advertencias para marcadores desconocidos.
  • Usar skip para ocultar errores: si la prueba documenta un error conocido, suele corresponder xfail.
  • Dejar xfail para siempre: revisa periódicamente los fallos esperados.
  • Crear demasiados marcadores: usa pocos nombres claros y útiles.
  • Confundir -m y -k: -m filtra marcadores; -k filtra nombres.

29.21 Comandos usados en este tema

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

29.22 Qué debes recordar de este tema

  • Los marcadores clasifican pruebas.
  • -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.
  • Los marcadores personalizados deberían registrarse en pytest.ini.

29.23 Conclusión

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.