10. Marcadores en pytest para clasificar pruebas automatizadas

10.1 Objetivo del tema

En el tema anterior seleccionamos pruebas por archivo, carpeta, nombre y expresión. Ahora agregaremos una forma más explícita de clasificación: los marcadores de pytest.

Los marcadores permiten etiquetar pruebas como lento, regresion, critica, config u otras categorías propias del proyecto. Luego podemos ejecutar o excluir grupos completos.

Objetivo práctico: clasificar pruebas con @pytest.mark y ejecutarlas por categoría usando python -m pytest -m.

10.2 Qué es un marcador

Un marcador es una etiqueta que se aplica a una prueba. Esa etiqueta no cambia la lógica de la prueba, pero permite clasificarla y seleccionarla.

Ejemplo:

import pytest


@pytest.mark.critica
def test_calcular_total_con_varios_productos_devuelve_suma_total():
    assert True

En este caso, la prueba queda marcada como critica.

10.3 Para qué sirven los marcadores

Los marcadores ayudan a responder preguntas como:

  • ¿Cuáles son las pruebas críticas?
  • ¿Qué pruebas son lentas?
  • ¿Qué pruebas corresponden a regresión?
  • ¿Qué pruebas verifican configuración?
  • ¿Qué pruebas puedo ejecutar rápidamente durante el desarrollo?

La clasificación permite ejecutar una parte de la suite por intención, no solo por archivo o nombre.

10.4 Declarar marcadores en pytest.ini

Antes de usarlos, conviene declararlos en pytest.ini:

[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --strict-markers --tb=short
markers =
    lento: pruebas que tardan más tiempo en ejecutarse
    regresion: pruebas importantes para detectar regresiones
    config: pruebas relacionadas con configuración del proyecto
    critica: pruebas indispensables para validar el comportamiento principal
    carrito: pruebas relacionadas con el carrito de compras
    texto: pruebas relacionadas con normalización y manejo de textos

La opción --strict-markers hace que pytest avise con error si usamos un marcador no declarado.

10.5 Marcar pruebas del carrito

Modifica tests/test_carrito.py para marcar las pruebas del carrito:

import pytest

from app.carrito import calcular_total, carrito_esta_vacio


@pytest.mark.carrito
@pytest.mark.critica
def test_calcular_total_con_un_producto_devuelve_precio_por_cantidad():
    productos = [{"precio": 100, "cantidad": 2}]

    assert calcular_total(productos) == 200


@pytest.mark.carrito
@pytest.mark.regresion
def test_calcular_total_con_varios_productos_devuelve_suma_total():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    assert calcular_total(productos) == 350

Una prueba puede tener más de un marcador.

10.6 Ejecutar pruebas por marcador

Para ejecutar solo pruebas del carrito:

python -m pytest -m carrito

Para ejecutar solo pruebas críticas:

python -m pytest -m critica

Para ejecutar solo pruebas de regresión:

python -m pytest -m regresion

10.7 Excluir pruebas por marcador

También podemos excluir grupos. Por ejemplo, para ejecutar todo menos las pruebas lentas:

python -m pytest -m "not lento"

Este comando es útil cuando queremos una ejecución rápida durante el desarrollo.

10.8 Combinar marcadores

Los marcadores se pueden combinar con expresiones:

python -m pytest -m "carrito and critica"

Ejecutar pruebas que sean de carrito o texto:

python -m pytest -m "carrito or texto"

Ejecutar regresión excluyendo lentas:

python -m pytest -m "regresion and not lento"

10.9 Marcar una clase completa

Si usas clases de prueba, puedes marcar una clase completa:

import pytest


@pytest.mark.carrito
class TestCarrito:
    def test_carrito_vacio_devuelve_true(self):
        assert True

    def test_carrito_con_productos_devuelve_false(self):
        assert True

Todas las pruebas dentro de la clase reciben el marcador carrito.

10.10 Marcar todo un módulo

También puedes aplicar un marcador a todo un archivo usando pytestmark:

import pytest


pytestmark = pytest.mark.carrito

Si agregas esa línea en tests/test_carrito.py, todas las pruebas del archivo quedan marcadas como carrito.

Usa pytestmark cuando todo el archivo pertenece claramente a la misma categoría.

10.11 Marcar pruebas lentas

Supongamos que una prueba tarda más que las demás. Podemos marcarla como lento:

import time
import pytest


@pytest.mark.lento
def test_proceso_lento_de_ejemplo():
    time.sleep(1)

    assert True

Luego podemos excluirla durante una ejecución rápida:

python -m pytest -m "not lento"

10.12 Diferencia entre -k y -m

La opción -k selecciona por nombre o expresión textual. La opción -m selecciona por marcador.

Ejemplo con nombre:

python -m pytest -k carrito

Ejemplo con marcador:

python -m pytest -m carrito

-k depende de cómo se llaman las pruebas. -m depende de una clasificación explícita.

10.13 Usar marcadores desde run_tests.py

Podemos agregar al script un argumento para elegir marcador:

parser.add_argument("--marcador", help="Ejecuta pruebas con el marcador indicado")

Luego, en construir_comando:

if args.marcador:
    comando.extend(["-m", args.marcador])

Con esto podrás ejecutar:

python run_tests.py --marcador critica
python run_tests.py --marcador "regresion and not lento"

10.14 Ajustar la opción --rapido

Si tu script ya tiene --rapido, puede seguir usando el marcador lento:

if args.rapido:
    comando.extend(["-m", "not lento"])

Esto hace que el comando:

python run_tests.py --rapido

ejecute todo excepto las pruebas marcadas como lentas.

10.15 Evitar demasiados marcadores

Los marcadores son útiles, pero no conviene crear una etiqueta para cada detalle. Una suite con demasiados marcadores se vuelve difícil de entender.

Buenas categorías iniciales pueden ser:

  • critica: pruebas indispensables.
  • regresion: pruebas importantes para evitar errores repetidos.
  • lento: pruebas que no deben ejecutarse siempre.
  • config: pruebas de configuración.
  • Un marcador por área funcional cuando realmente ayuda, como carrito o texto.

10.16 Ver marcadores disponibles

pytest permite listar los marcadores registrados:

python -m pytest --markers

Este comando muestra marcadores propios y marcadores incorporados de pytest. Es útil para revisar si la documentación de pytest.ini quedó clara.

10.17 Documentar marcadores en el README

Agrega una sección al README.md:

## Marcadores de pruebas

- critica: pruebas indispensables para validar el comportamiento principal.
- regresion: pruebas importantes para detectar regresiones.
- lento: pruebas que tardan más tiempo en ejecutarse.
- config: pruebas relacionadas con configuración.
- carrito: pruebas relacionadas con el carrito de compras.
- texto: pruebas relacionadas con normalización y manejo de textos.

Ejecutar pruebas críticas:

python -m pytest -m critica

Ejecutar todo excepto pruebas lentas:

python -m pytest -m "not lento"

10.18 Problemas frecuentes

  • pytest informa un marcador desconocido: decláralo en pytest.ini.
  • No se ejecuta ninguna prueba: revisa que el marcador esté aplicado a alguna prueba.
  • La expresión falla: usa comillas cuando contiene espacios, and, or o not.
  • Hay demasiados marcadores: conserva solo los que aportan valor real para seleccionar o documentar.
  • Confundes -k con -m: recuerda que -k busca por nombre y -m por marcador.

10.19 Ejercicio práctico

Clasifica las pruebas creadas hasta ahora:

  • Marca las pruebas de test_config.py como config.
  • Marca las pruebas principales de test_carrito.py como carrito.
  • Marca al menos una prueba importante como critica.
  • Marca una prueba de ejemplo como lento.

Luego ejecuta:

python -m pytest -m config
python -m pytest -m critica
python -m pytest -m "not lento"

10.20 Solución propuesta

Ejemplo en tests/test_config.py:

import pytest

from app.config import obtener_ambiente


@pytest.mark.config
def test_config_obtener_ambiente_devuelve_local():
    assert obtener_ambiente() == "local"

Ejemplo en tests/test_carrito.py:

import pytest

from app.carrito import calcular_total


@pytest.mark.carrito
@pytest.mark.critica
def test_calcular_total_con_varios_productos_devuelve_suma_total():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    assert calcular_total(productos) == 350

10.21 Lista de verificación

Antes de continuar con el próximo tema, verifica lo siguiente:

  • Los marcadores están declarados en pytest.ini.
  • --strict-markers está activo en addopts.
  • Las pruebas pueden marcarse con @pytest.mark.nombre.
  • Puedes ejecutar pruebas por marcador con python -m pytest -m marcador.
  • Puedes excluir pruebas con python -m pytest -m "not lento".
  • El script run_tests.py puede usar marcadores si agregaste --marcador.
  • El README documenta los marcadores principales.

10.22 Conclusión

En este tema usamos marcadores de pytest para clasificar pruebas automatizadas. Esto permite ejecutar grupos por intención: críticas, lentas, de regresión, de configuración o de un área funcional.

Los marcadores serán la base del próximo tema, donde separaremos pruebas rápidas, lentas, críticas y de regresión con criterios más claros.