En el tema anterior creamos un proyecto base con una carpeta para el código y otra para las pruebas. En este tema vamos a mejorar esa organización para que la suite automatizada pueda crecer sin volverse difícil de mantener.
Trabajaremos con una estructura práctica para proyectos Python: código de aplicación, pruebas por nivel o responsabilidad, datos de prueba, utilidades, configuración y reportes generados.
Cuando una suite tiene pocas pruebas, casi cualquier estructura parece suficiente. El problema aparece cuando se agregan más módulos, más escenarios, más datos y más personas trabajando sobre el mismo proyecto.
Una estructura clara reduce errores como estos:
Partiremos de esta organización:
automatizacion-pruebas-python/
|-- app/
| |-- __init__.py
| |-- calculadora.py
| `-- textos.py
|-- tests/
| |-- data/
| |-- helpers/
| |-- test_calculadora.py
| `-- test_textos.py
|-- reports/
|-- pytest.ini
|-- requirements.txt
`-- run_tests.py
La idea principal es separar responsabilidades. El código que se prueba va en app. Las pruebas y sus recursos van en tests. Los reportes generados van en reports.
Desde la raíz del proyecto creado en el tema anterior, ejecuta:
mkdir tests\data
mkdir tests\helpers
mkdir reports
En Linux o macOS, puedes usar:
mkdir -p tests/data tests/helpers reports
Estas carpetas todavía pueden estar vacías. Las iremos usando a medida que la suite lo necesite.
La carpeta app contiene el código de la aplicación o del módulo que estamos verificando. No debería contener archivos de prueba.
Por ejemplo:
app/
|-- __init__.py
|-- calculadora.py
`-- textos.py
Separar el código de las pruebas hace que los imports sean más claros y que la suite pueda crecer sin confundirse con la lógica de negocio.
La carpeta tests contiene las pruebas automatizadas. En un proyecto pequeño puede tener archivos directamente, como test_calculadora.py. En un proyecto más grande, puede dividirse en subcarpetas.
Una organización posible es:
tests/
|-- unit/
|-- integration/
|-- data/
`-- helpers/
En este curso iremos agregando complejidad de forma gradual. Por ahora mantendremos archivos simples en tests, pero dejaremos preparadas carpetas para datos y utilidades.
La carpeta tests/data sirve para guardar datos usados por las pruebas: archivos JSON, CSV, TXT u otros recursos pequeños.
Por ejemplo:
tests/data/
|-- usuarios_validos.json
|-- productos.csv
`-- mensajes.txt
Estos datos deben ser controlados y predecibles. No conviene que una prueba dependa de archivos externos que cambian sin aviso.
La carpeta tests/helpers contiene funciones auxiliares usadas por varias pruebas. Por ejemplo, constructores de datos, validadores comunes o funciones para leer archivos de prueba.
Crea el archivo tests/helpers/__init__.py:
type nul > tests\helpers\__init__.py
En Linux o macOS:
touch tests/helpers/__init__.py
Esto permite importar helpers desde las pruebas de forma ordenada.
Crea el archivo tests/helpers/textos_helper.py:
def crear_texto_con_espacios(texto):
return f" {texto} "
def crear_texto_en_mayusculas(texto):
return texto.upper()
Este helper no prueba nada por sí mismo. Solo prepara datos que pueden ser reutilizados por varias pruebas.
Modifica o crea tests/test_textos.py con este contenido:
from app.textos import normalizar_texto
from tests.helpers.textos_helper import crear_texto_con_espacios, crear_texto_en_mayusculas
def test_normalizar_texto_quita_espacios_externos():
texto = crear_texto_con_espacios("python")
assert normalizar_texto(texto) == "python"
def test_normalizar_texto_convierte_a_minusculas():
texto = crear_texto_en_mayusculas("pytest")
assert normalizar_texto(texto) == "pytest"
La prueba queda más expresiva: se entiende que el dato fue preparado para representar un caso concreto.
Para importar desde tests.helpers, crea también tests/__init__.py:
type nul > tests\__init__.py
En Linux o macOS:
touch tests/__init__.py
Con esto, Python puede tratar tests como paquete y resolver el import del helper.
Ejecuta nuevamente:
python -m pytest -v
Si aparece un error de importación, revisa que estés ejecutando el comando desde la raíz del proyecto y que existan los archivos tests/__init__.py y tests/helpers/__init__.py.
La carpeta reports se usará para guardar salidas generadas por herramientas: reportes HTML, archivos XML, logs o resultados temporales.
Por ahora solo la dejamos creada. En temas posteriores aprenderemos a generar reportes concretos.
Los archivos que configuran el proyecto suelen estar en la raíz. En nuestro caso:
pytest.ini: configuración de pytest.requirements.txt: dependencias del proyecto.run_tests.py: script simple para ejecutar la suite.README.md: instrucciones básicas para usar el proyecto.Ubicarlos en la raíz facilita encontrarlos y ejecutarlos desde un lugar predecible.
Una estructura clara también depende de nombres consistentes. Usaremos estas reglas:
test_.test_.textos_helper.py.usuarios_validos.json.Evita estas prácticas cuando la suite empieza a crecer:
app.varios, misc o cosas.test_1.py o pruebas.py.Después de aplicar los cambios, el proyecto debería verse así:
automatizacion-pruebas-python/
|-- .venv/
|-- app/
| |-- __init__.py
| |-- calculadora.py
| `-- textos.py
|-- tests/
| |-- __init__.py
| |-- data/
| |-- helpers/
| | |-- __init__.py
| | `-- textos_helper.py
| |-- test_calculadora.py
| `-- test_textos.py
|-- reports/
|-- pytest.ini
|-- README.md
|-- requirements.txt
`-- run_tests.py
Agrega un nuevo módulo llamado app/usuarios.py con una función crear_nombre_usuario. La función debe recibir nombre y apellido, quitar espacios, convertir a minúsculas y devolver el usuario con formato nombre.apellido.
Luego organiza las pruebas de esta forma:
tests/helpers/usuarios_helper.py para preparar nombres con espacios y mayúsculas.tests/test_usuarios.py para probar la función.python -m pytest -v.Archivo app/usuarios.py:
def crear_nombre_usuario(nombre, apellido):
nombre_limpio = nombre.strip().lower()
apellido_limpio = apellido.strip().lower()
return f"{nombre_limpio}.{apellido_limpio}"
Archivo tests/helpers/usuarios_helper.py:
def crear_nombre_con_espacios(nombre):
return f" {nombre} "
def crear_apellido_en_mayusculas(apellido):
return apellido.upper()
Archivo tests/test_usuarios.py:
from app.usuarios import crear_nombre_usuario
from tests.helpers.usuarios_helper import crear_apellido_en_mayusculas, crear_nombre_con_espacios
def test_crear_nombre_usuario_une_nombre_y_apellido():
assert crear_nombre_usuario("ana", "perez") == "ana.perez"
def test_crear_nombre_usuario_quita_espacios_del_nombre():
nombre = crear_nombre_con_espacios("ana")
assert crear_nombre_usuario(nombre, "perez") == "ana.perez"
def test_crear_nombre_usuario_convierte_apellido_a_minusculas():
apellido = crear_apellido_en_mayusculas("perez")
assert crear_nombre_usuario("ana", apellido) == "ana.perez"
Antes de continuar con el próximo tema, verifica lo siguiente:
app.tests.En este tema organizamos el proyecto para que la automatización pueda crecer de forma ordenada. Separar código, pruebas, datos, helpers y reportes evita confusión y facilita el mantenimiento.
Una buena estructura no garantiza buenas pruebas, pero crea las condiciones para escribirlas, ejecutarlas y modificarlas con menos esfuerzo. En el próximo tema instalaremos herramientas adicionales y dejaremos preparadas las dependencias útiles para el resto del curso.