3. Estructura recomendada de carpetas para una suite automatizada

3.1 Objetivo del tema

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.

Objetivo práctico: reorganizar el proyecto para que sea claro dónde ubicar cada prueba, dato, fixture, helper y archivo generado.

3.2 Por qué importa la estructura

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:

  • Pruebas duplicadas porque nadie encuentra las existentes.
  • Archivos de datos mezclados con el código de la aplicación.
  • Funciones auxiliares repetidas en varios archivos de prueba.
  • Reportes generados subidos accidentalmente al repositorio.
  • Pruebas lentas mezcladas con pruebas rápidas sin criterio.

3.3 Estructura inicial recomendada

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.

3.4 Crear las nuevas carpetas

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.

3.5 Carpeta app

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.

3.6 Carpeta tests

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.

3.7 Carpeta tests/data

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.

3.8 Carpeta tests/helpers

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.

3.9 Crear un helper para datos de texto

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.

3.10 Usar el helper en una prueba

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.

3.11 Agregar __init__.py en tests

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.

3.12 Ejecutar la suite después de reorganizar

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.

3.13 Carpeta reports

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 generados no suelen escribirse a mano. Por eso conviene tenerlos separados del código fuente y de las pruebas.

3.14 Archivos de configuración en la raíz

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.

3.15 Convenciones de nombres

Una estructura clara también depende de nombres consistentes. Usaremos estas reglas:

  • Los archivos de prueba comienzan con test_.
  • Las funciones de prueba comienzan con test_.
  • Los helpers describen la utilidad que ofrecen, por ejemplo textos_helper.py.
  • Los datos de prueba tienen nombres relacionados con el caso, por ejemplo usuarios_validos.json.
  • Las carpetas usan nombres cortos, claros y en minúsculas.

3.16 Qué no conviene hacer

Evita estas prácticas cuando la suite empieza a crecer:

  • Guardar pruebas dentro de la carpeta app.
  • Crear una carpeta llamada varios, misc o cosas.
  • Duplicar helpers en varios archivos.
  • Mezclar datos reales con datos de prueba.
  • Guardar reportes generados junto al código fuente.
  • Usar nombres genéricos como test_1.py o pruebas.py.

3.17 Estructura final recomendada para esta etapa

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

3.18 Ejercicio práctico

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:

  • Crea tests/helpers/usuarios_helper.py para preparar nombres con espacios y mayúsculas.
  • Crea tests/test_usuarios.py para probar la función.
  • Ejecuta toda la suite con python -m pytest -v.

3.19 Solución propuesta

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"

3.20 Lista de verificación

Antes de continuar con el próximo tema, verifica lo siguiente:

  • El código de aplicación está separado en app.
  • Las pruebas están dentro de tests.
  • Los datos de prueba tienen una carpeta propia.
  • Los helpers reutilizables están separados de las pruebas.
  • Los reportes generados tienen una carpeta propia.
  • La suite completa se ejecuta desde la raíz del proyecto.
  • Los nombres de carpetas, archivos y funciones son claros y consistentes.

3.21 Conclusión

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.