Una suite automatizada no solo debe funcionar: también debe poder leerse, mantenerse y diagnosticarse rápidamente cuando falla. Para eso, los nombres son fundamentales.
En este tema definiremos convenciones para nombrar archivos de prueba, funciones de prueba, carpetas, datos y helpers. El objetivo es que cada nombre explique con claridad qué se está verificando y dónde debe ubicarse cada elemento.
Cuando una prueba falla, el nombre es una de las primeras pistas. Un nombre como test_1 no ayuda a entender el problema. En cambio, test_calcular_total_aplica_descuento_al_cliente_frecuente comunica mucho más.
Buenos nombres ayudan a:
En este curso usaremos archivos que comienzan con test_:
tests/
|-- test_calculadora.py
|-- test_textos.py
`-- test_usuarios.py
Esta convención coincide con la configuración que definimos en pytest.ini:
python_files = test_*.py
Si un archivo no sigue esta convención, pytest podría no detectarlo automáticamente.
Las funciones de prueba también deben comenzar con test_:
def test_sumar_devuelve_la_suma_de_dos_numeros():
assert sumar(2, 3) == 5
Evita nombres demasiado genéricos:
def test_ok():
assert sumar(2, 3) == 5
El primer nombre explica el comportamiento. El segundo solo dice que algo debería estar bien, pero no indica qué.
Usaremos este formato como guía:
test_funcion_o_comportamiento_condicion_resultado_esperado
Ejemplos:
def test_dividir_con_divisor_cero_lanza_value_error():
...
def test_normalizar_texto_con_espacios_externos_devuelve_texto_limpio():
...
def test_crear_nombre_usuario_con_mayusculas_devuelve_usuario_en_minusculas():
...
No todos los nombres necesitan tener todas las partes, pero sí deben comunicar la intención principal.
Una buena prueba describe una conducta observable. Por eso es mejor nombrar la prueba por lo que verifica, no por cómo está implementado el código.
Menos claro:
def test_if_descuento():
...
Más claro:
def test_calcular_precio_final_con_descuento_devuelve_precio_reducido():
...
El segundo nombre permite entender la regla incluso sin abrir el código de la prueba.
Cuando la prueba espera una excepción, conviene indicarlo en el nombre:
def test_dividir_con_divisor_cero_lanza_value_error():
with pytest.raises(ValueError):
dividir(10, 0)
También podríamos usar palabras como rechaza, invalido o error si hacen el nombre más natural:
def test_crear_nombre_usuario_rechaza_nombre_vacio():
...
Los nombres descriptivos son buenos, pero tampoco deben convertirse en párrafos. Si un nombre se vuelve demasiado largo, tal vez la prueba verifica demasiadas cosas.
Ejemplo excesivo:
def test_calcular_precio_final_cuando_el_precio_es_cien_y_el_descuento_es_diez_entonces_el_resultado_debe_ser_noventa():
...
Versión más práctica:
def test_calcular_precio_final_con_descuento_devuelve_precio_reducido():
...
Las carpetas deben tener nombres cortos, en minúsculas y relacionados con su responsabilidad:
tests/
|-- data/
|-- helpers/
|-- unit/
`-- integration/
Evita carpetas con nombres ambiguos:
tests/
|-- cosas/
|-- varios/
`-- pruebas_nuevas/
Una carpeta debe responder claramente qué tipo de contenido contiene.
Los archivos de datos deben indicar qué contienen y para qué caso se usan:
tests/data/
|-- usuarios_validos.json
|-- usuarios_invalidos.json
|-- productos_con_descuento.csv
`-- mensajes_de_error.txt
Evita nombres como datos1.json, archivo.csv o prueba.txt. Esos nombres obligan a abrir el archivo para saber si sirve.
Los helpers deben expresar qué ayudan a construir o verificar:
tests/helpers/
|-- usuarios_helper.py
|-- textos_helper.py
`-- archivos_helper.py
Dentro de un helper, usa nombres de funciones orientados a la tarea:
def crear_usuario_valido():
...
def crear_usuario_sin_email():
...
def leer_json_de_prueba(nombre_archivo):
...
Las fixtures deben nombrarse por el dato o recurso que entregan, no por el mecanismo interno que usan.
Menos claro:
def fixture_1():
...
Más claro:
def usuario_valido():
...
def carpeta_temporal_con_archivos():
...
Veremos fixtures en profundidad más adelante, pero conviene adoptar desde ahora nombres que expliquen su propósito.
Supongamos que tenemos esta prueba:
def test_texto():
assert normalizar_texto(" PYTHON ") == "python"
Funciona, pero el nombre no comunica lo suficiente. Podemos mejorarla así:
def test_normalizar_texto_con_espacios_y_mayusculas_devuelve_texto_limpio():
assert normalizar_texto(" PYTHON ") == "python"
Si falla, el nombre ya indica qué comportamiento dejó de cumplirse.
Si una prueba está marcada como config, su nombre también debería reflejar que se relaciona con configuración:
import pytest
from app.config import obtener_timeout
@pytest.mark.config
def test_config_obtener_timeout_devuelve_valor_definido_en_env():
assert obtener_timeout() == 5
No es obligatorio repetir siempre el marcador en el nombre, pero puede ser útil cuando el archivo contiene pruebas de distintos tipos.
Una ventaja de nombres claros es que podemos seleccionar pruebas con -k:
python -m pytest -k usuario
Ese comando ejecuta pruebas cuyo nombre contiene usuario. También podemos combinar palabras:
python -m pytest -k "usuario and invalido"
Esta forma de selección funciona mucho mejor si los nombres son descriptivos.
Revisa los archivos creados hasta ahora y ajusta los nombres que sean demasiado generales. Por ejemplo:
test_sumar puede cambiarse a test_sumar_devuelve_la_suma_de_dos_numeros.test_dividir_error puede cambiarse a test_dividir_con_divisor_cero_lanza_value_error.datos.json puede cambiarse a usuarios_validos.json si contiene usuarios válidos.helper.py puede cambiarse a usuarios_helper.py o textos_helper.py.Cada vez que renombres archivos o funciones de prueba, ejecuta la suite completa:
python -m pytest
Esto confirma que pytest sigue detectando las pruebas y que los imports no se rompieron.
Agrega una sección breve al README.md del proyecto:
## Convenciones de nombres
- Los archivos de prueba comienzan con test_.
- Las funciones de prueba comienzan con test_.
- Las pruebas deben describir comportamiento, condición y resultado esperado.
- Los datos de prueba se guardan en tests/data.
- Los helpers reutilizables se guardan en tests/helpers.
- No usar nombres genéricos como test_1, datos.json o helper.py.
Una guía corta ayuda a mantener consistencia cuando el proyecto crece.
test_.tests/helpers si solo lo usan las pruebas.tests/data para datos pequeños y controlados.Crea el módulo app/cupones.py con una función validar_cupon. La función debe devolver True si el cupón tiene el texto DESC10 y False en cualquier otro caso.
Luego crea tests/test_cupones.py con nombres de prueba descriptivos para estos casos:
Archivo app/cupones.py:
def validar_cupon(cupon):
return cupon.strip().upper() == "DESC10"
Archivo tests/test_cupones.py:
from app.cupones import validar_cupon
def test_validar_cupon_con_codigo_correcto_devuelve_true():
assert validar_cupon("DESC10") is True
def test_validar_cupon_con_codigo_incorrecto_devuelve_false():
assert validar_cupon("DESC20") is False
def test_validar_cupon_con_codigo_en_minusculas_devuelve_true():
assert validar_cupon("desc10") is True
def test_validar_cupon_con_espacios_externos_devuelve_true():
assert validar_cupon(" DESC10 ") is True
Ejecuta:
python -m pytest tests/test_cupones.py
Antes de continuar con el próximo tema, verifica lo siguiente:
test_.test_.python -m pytest después de renombrar.En este tema definimos convenciones para nombrar pruebas, carpetas, datos y utilidades. Aunque parezca un detalle menor, los nombres influyen directamente en la mantenibilidad de una suite automatizada.
Una prueba con buen nombre funciona como documentación ejecutable. En el próximo tema veremos cómo automatizar la ejecución de una suite completa desde la terminal usando los criterios y comandos que ya preparamos.