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.
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.
Crea un proyecto nuevo:
mkdir calidad-demo
cd calidad-demo
Instala las herramientas necesarias:
python -m pip install pytest black ruff
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
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.
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
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
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.
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
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
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.
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.
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.
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.
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.
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.
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
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
Las tres prácticas se complementan. Ninguna reemplaza por completo a las otras.
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
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.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.