En el ámbito de la automatización de pruebas, la ejecución reiterada de un mismo caso de prueba es una técnica fundamental para validar la estabilidad de un sistema o identificar errores intermitentes (flaky tests). A continucaión, se presentan cuatro enfoques técnicos para implementar repeticiones en Pytest, integrando un seguimiento detallado del progreso de cada iteración.
1. Implementación mediante un decorador personalizado
Este enfoque utiliza la metaprogramación de Python para envolver la función de prueba. Permite encapsular la lógica de repetición sin modificar la estructura interna del test.
import pytest
import logging
from datetime import datetime
# Configuración básica de logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def iterar_prueba(max_intentos):
def decorador(func):
def envoltura(*args, **kwargs):
for paso in range(max_intentos):
restantes = max_intentos - paso - 1
logger.info(f"Iteración actual: {paso + 1} | Pendientes: {restantes}")
func(*args, **kwargs)
return envoltura
return decorador
class TestSuiteDecorador:
TOTAL = 4
@iterar_prueba(TOTAL)
def test_verificacion_tiempo(self):
timestamp = datetime.now().strftime("%H:%M:%S")
logger.info(f"Ejecutado a las: {timestamp}")
assert 10 > 5
2. Uso de la parametrización nativa de Pytest
La funcionalidad @pytest.mark.parametrize es la forma más "estándar" de manejar repeticiones, ya que Pytest trata cada iteración como un sub-caso de prueba independiente en el reporte final.
import pytest
import logging
logger = logging.getLogger(__name__)
class TestSuiteParametrizada:
LIMITE_EJECUCION = 3
@pytest.mark.parametrize("indice", range(LIMITE_EJECUCION))
def test_flujo_repetitivo(self, indice):
vuelta_actual = indice + 1
faltantes = self.LIMITE_EJECUCION - vuelta_actual
logger.info(f"Ejecución: {vuelta_actual} de {self.LIMITE_EJECUCION}. Restan: {faltantes}")
assert True
3. Ciclos de control internos
Es la técnica más sencilla y directa. Se utiliza un bucle for dentro del cuerpo del método de prueba. Es útil cuando se desea verificar una secuencia de acciones rápidas sin generar múltiples entradas en el reporte de Pytest.
import pytest
import logging
logger = logging.getLogger(__name__)
class TestSuiteCicloInterno:
VOLUMEN_REPETICIONES = 5
def test_logica_interna(self):
for i in range(self.VOLUMEN_REPETICIONES):
actual = i + 1
por_ejecutar = self.VOLUMEN_REPETICIONES - actual
logger.info(f"Procesando iteración {actual}. Quedan {por_ejecutar} en cola.")
# Lógica de validación
resultado = 2**2
assert resultado == 4
4. Gestión de estado con atributos de clase
Este método es ideal cuando se utiliza el plugin pytest-repeat. Permite llevar un contador global dentro de la clase para rastrear el progreso a través de múltiples llamadas al mismo método.
import pytest
import logging
logger = logging.getLogger(__name__)
class TestSuiteContadorEstado:
CICLOS_TOTALES = 3
_contador_global = 0
@pytest.mark.repeat(CICLOS_TOTALES)
def test_con_estado_estatico(self):
TestSuiteContadorEstado._contador_global += 1
actual = TestSuiteContadorEstado._contador_global
pendientes = self.CICLOS_TOTALES - actual
logger.info(f"Contador de clase: {actual} | Faltan por completar: {pendientes}")
assert "pytest" in "framework pytest"
Consideraciones Técnicas
- Aislamiento de Pruebas: Al repetir pruebas, es vital asegurar que cada iteración comience desde un estado limpio (teardown/setup) para evitar efectos colaterales de datos residuales.
- Gestión de Logs: En ejecuciones masivas, el exceso de logs puede saturar el sistema de archivos o dificutlar el aálisis. Se recomienda usar niveles de log adecuados (INFO vs DEBUG).
- Colisión de Variables: Al usar atributos de clase (como en el método 4), se debe tener precaución si las pruebas se ejecutan en paralelo (usando
pytest-xdist), ya que el estado compartido podría no ser thread-safe.