33. Integrar pruebas con formateo y análisis estático básico

33.1 Objetivo del tema

Las pruebas verifican comportamiento, pero no alcanzan para detectar todos los problemas de calidad. Un proyecto también necesita formato consistente y revisión automática de errores comunes.

En este tema integraremos pytest con herramientas de formateo y análisis estático básico.

Idea clave: una rutina de calidad simple puede ejecutar formato, análisis estático y pruebas antes de compartir cambios.

33.2 Herramientas que usaremos

  • pytest: ejecuta las pruebas.
  • black: aplica un formato uniforme al código Python.
  • ruff: detecta errores frecuentes, imports sin usar, variables innecesarias y otros problemas de estilo.

Estas herramientas no reemplazan el criterio del programador, pero reducen trabajo repetitivo y ayudan a encontrar problemas temprano.

33.3 Crear una carpeta de práctica

Crea un proyecto nuevo:

mkdir calidad-demo
cd calidad-demo

Instala las herramientas necesarias:

python -m pip install pytest black ruff

33.4 Crear la estructura del proyecto

Crea estas carpetas y archivos:

mkdir src
mkdir src\ventas
mkdir tests
New-Item src\ventas\__init__.py -ItemType File
New-Item src\ventas\descuentos.py -ItemType File
New-Item tests\test_descuentos.py -ItemType File

33.5 Crear un módulo con algunos detalles mejorables

En src\ventas\descuentos.py escribe:

import math

def aplicar_descuento(precio, porcentaje):
    if precio < 0:
        raise ValueError("El precio no puede ser negativo")
    if porcentaje < 0 or porcentaje > 100:
        raise ValueError("El porcentaje debe estar entre 0 y 100")
    return precio - precio * porcentaje / 100

def calcular_total(items):
    total = 0
    for item in items:
        total = total + item["precio"]
    return total

El código funciona, pero tiene detalles: un import no usado, formato irregular y una expresión que puede escribirse de manera más clara.

33.6 Crear pruebas

En tests\test_descuentos.py escribe:

import pytest

from ventas.descuentos import aplicar_descuento, calcular_total


def test_aplicar_descuento():
    assert aplicar_descuento(1000, 10) == 900


def test_aplicar_descuento_con_porcentaje_invalido():
    with pytest.raises(ValueError):
        aplicar_descuento(1000, 150)


def test_calcular_total():
    items = [
        {"nombre": "Teclado", "precio": 30000},
        {"nombre": "Mouse", "precio": 12000},
    ]

    assert calcular_total(items) == 42000

33.7 Configurar pytest

Crea un archivo pytest.ini para que pytest encuentre el paquete dentro de src:

[pytest]
pythonpath = src
testpaths = tests
addopts = -ra

Ejecuta las pruebas:

python -m pytest

33.8 Ejecutar black

black puede revisar si el formato es correcto sin modificar archivos:

python -m black --check src tests

Si encuentra archivos que necesitan formato, puedes aplicar los cambios con:

python -m black src tests

Después de formatear, el código conserva el mismo comportamiento, pero queda con una estructura consistente.

33.9 Ejecutar ruff

ruff analiza el código y muestra problemas frecuentes:

python -m ruff check src tests

En nuestro ejemplo puede detectar que math está importado pero no se usa.

F401 `math` imported but unused

Algunos problemas se pueden corregir automáticamente:

python -m ruff check src tests --fix

33.10 Código corregido

Luego de aplicar formato y quitar el import innecesario, src\ventas\descuentos.py puede quedar así:

def aplicar_descuento(precio, porcentaje):
    if precio < 0:
        raise ValueError("El precio no puede ser negativo")
    if porcentaje < 0 or porcentaje > 100:
        raise ValueError("El porcentaje debe estar entre 0 y 100")
    return precio - precio * porcentaje / 100


def calcular_total(items):
    total = 0
    for item in items:
        total += item["precio"]
    return total

33.11 Orden recomendado de ejecución

Una rutina simple puede ejecutarse en este orden:

python -m black src tests
python -m ruff check src tests
python -m pytest

Primero se normaliza el formato, luego se buscan problemas estáticos y finalmente se comprueba el comportamiento con pruebas.

33.12 Modo de verificación

Cuando no quieres modificar archivos, por ejemplo en una integración continua, puedes usar:

python -m black --check src tests
python -m ruff check src tests
python -m pytest

Así la ejecución falla si el código no cumple el formato o si hay problemas detectados por el analizador.

33.13 Configurar black en pyproject.toml

Crea un archivo pyproject.toml si quieres guardar configuración de las herramientas:

[tool.black]
line-length = 88
target-version = ["py312"]

El largo de línea de 88 caracteres es el valor habitual de black.

33.14 Configurar ruff en pyproject.toml

En el mismo archivo puedes agregar:

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = []

Con esta configuración básica, ruff revisa errores de estilo, problemas lógicos simples e imports.

33.15 Qué significan esas reglas

  • E: errores de estilo similares a los detectados por pycodestyle.
  • F: errores detectados por pyflakes, como imports sin usar o nombres no definidos.
  • I: ordenamiento de imports.

Para empezar conviene usar pocas reglas y entenderlas. Luego puedes ampliar la configuración según el proyecto.

33.16 Usar ruff para ordenar imports

Si activaste la regla I, puedes dejar que ruff ordene imports automáticamente:

python -m ruff check src tests --fix

Esto evita discusiones manuales sobre el orden de imports y mantiene el proyecto consistente.

33.17 Agregar un script de calidad en Windows

Para no escribir todos los comandos cada vez, puedes crear quality.ps1:

python -m black --check src tests
python -m ruff check src tests
python -m pytest

Luego ejecútalo desde PowerShell:

.\quality.ps1

33.18 Alternativa con tox

Si quieres integrar esta rutina con lo visto en el tema anterior, puedes crear un entorno de calidad en tox.ini:

[tox]
envlist = py312, quality
skip_missing_interpreters = true

[testenv]
deps =
    pytest
commands =
    python -m pytest

[testenv:quality]
deps =
    black
    ruff
commands =
    python -m black --check src tests
    python -m ruff check src tests

Después ejecutas todo con:

python -m tox

33.19 Diferencia entre formateo, lint y pruebas

  • Formateo: cambia la apariencia del código sin cambiar su comportamiento.
  • Lint o análisis estático: detecta problemas leyendo el código sin ejecutarlo.
  • Pruebas: ejecutan el programa y verifican resultados concretos.

Las tres prácticas se complementan. Ninguna reemplaza por completo a las otras.

33.20 Errores frecuentes

  • Ejecutar solo pruebas: pueden quedar imports sin usar, nombres confusos o formato inconsistente.
  • Ejecutar solo lint: el código puede verse correcto pero fallar al ejecutarse.
  • Aplicar demasiadas reglas al principio: puede generar ruido y frenar el aprendizaje.
  • No automatizar comandos repetidos: aumenta la posibilidad de olvidar una verificación.
  • Corregir formato a mano: para eso conviene usar una herramienta automática.

33.21 Comandos usados en este tema

mkdir calidad-demo
cd calidad-demo
python -m pip install pytest black ruff
mkdir src
mkdir src\ventas
mkdir tests
New-Item src\ventas\__init__.py -ItemType File
New-Item src\ventas\descuentos.py -ItemType File
New-Item tests\test_descuentos.py -ItemType File
python -m pytest
python -m black --check src tests
python -m black src tests
python -m ruff check src tests
python -m ruff check src tests --fix

33.22 Qué debes recordar de este tema

  • black aplica formato automático.
  • ruff detecta problemas estáticos y puede corregir algunos con --fix.
  • pytest verifica el comportamiento del código.
  • pyproject.toml puede centralizar configuración de herramientas.
  • Una rutina básica de calidad debe ser fácil de ejecutar y repetir.

33.23 Conclusión

En este tema integramos pruebas con formateo y análisis estático básico usando pytest, black y ruff. También vimos cómo ejecutar estas verificaciones manualmente, desde un script y desde tox.

En el próximo tema repasaremos errores frecuentes al testear en Python y cómo corregirlos.