2. Creación de un proyecto de ejemplo para probar

2.1 Objetivo del tema

En este tema construiremos un proyecto Python pequeño, pero con suficiente variedad para practicar testing durante los próximos temas. Tendrá funciones simples, validaciones, excepciones, una clase con estado y un archivo preparado para ejecutar algunas comprobaciones manuales.

La idea no es crear una aplicación grande. La idea es tener código concreto para probar. Si el código de ejemplo es claro, las pruebas también serán más fáciles de entender.

Proyecto del tema: una pequeña lógica de ventas con productos, descuentos, impuestos y un carrito de compras.

2.2 Crear la carpeta del proyecto

Desde la carpeta general del curso, crea un nuevo proyecto:

mkdir tienda-demo
cd tienda-demo

Este proyecto será independiente de los ejemplos anteriores. Así podremos practicar sin mezclar archivos de distintos temas.

Creación del proyecto tienda-demo en la terminal

2.3 Crear y activar el entorno virtual

Crea el entorno virtual:

python -m venv .venv
Actualización de pip e instalación de pytest

Actívalo en Windows PowerShell:

.venv\Scripts\Activate.ps1

En Linux o macOS:

source .venv/bin/activate

Actualiza pip e instala pytest:

python -m pip install --upgrade pip
python -m pip install pytest

2.4 Crear la estructura de carpetas

Para este proyecto usaremos una estructura un poco más ordenada que en los primeros ejemplos:

mkdir tienda
mkdir tests

Luego crea un archivo vacío llamado __init__.py dentro de la carpeta tienda. En Windows PowerShell:

New-Item tienda\__init__.py -ItemType File
Creación del archivo __init__.py en la carpeta tienda

En Linux o macOS:

touch tienda/__init__.py

Ese archivo indica que tienda puede usarse como paquete Python.

2.5 Estructura esperada

Por ahora la carpeta debería verse así:

tienda-demo/
|-- .venv/
|-- tienda/
|   `-- __init__.py
`-- tests/

El código principal irá dentro de tienda y las pruebas irán dentro de tests.

2.6 Crear el módulo precios.py

Dentro de la carpeta tienda, crea el archivo precios.py:

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_impuesto(precio, porcentaje_impuesto):
    if precio < 0:
        raise ValueError("El precio no puede ser negativo")
    if porcentaje_impuesto < 0:
        raise ValueError("El impuesto no puede ser negativo")

    return precio * porcentaje_impuesto / 100


def calcular_total(precio, descuento, impuesto):
    precio_con_descuento = aplicar_descuento(precio, descuento)
    importe_impuesto = calcular_impuesto(precio_con_descuento, impuesto)
    return precio_con_descuento + importe_impuesto

Este módulo tiene varios comportamientos interesantes para probar: cálculos correctos, datos inválidos y uso de funciones auxiliares.

Código del módulo precios.py con funciones de cálculo

2.7 Crear el módulo productos.py

Dentro de tienda, crea productos.py:

def normalizar_nombre(nombre):
    return nombre.strip().title()


def validar_producto(nombre, precio):
    if not nombre or not nombre.strip():
        raise ValueError("El nombre del producto es obligatorio")
    if precio <= 0:
        raise ValueError("El precio debe ser mayor que cero")

    return {
        "nombre": normalizar_nombre(nombre),
        "precio": precio,
    }

Este módulo nos permitirá practicar pruebas sobre cadenas, diccionarios, validaciones y excepciones.

2.8 Crear el módulo carrito.py

Ahora crea carrito.py dentro de tienda:

class Carrito:
    def __init__(self):
        self.items = []

    def agregar(self, producto, cantidad=1):
        if cantidad <= 0:
            raise ValueError("La cantidad debe ser mayor que cero")

        self.items.append({
            "producto": producto,
            "cantidad": cantidad,
        })

    def cantidad_total(self):
        return sum(item["cantidad"] for item in self.items)

    def subtotal(self):
        total = 0
        for item in self.items:
            total += item["producto"]["precio"] * item["cantidad"]
        return total

    def vaciar(self):
        self.items.clear()

Esta clase tiene estado interno. Agrega productos, calcula cantidades, calcula subtotales y permite vaciar el carrito. Más adelante la usaremos para practicar pruebas de clases y cambios de estado.

2.9 Crear un archivo de ejecución manual

Aunque el objetivo del curso es automatizar pruebas, un archivo de ejecución manual nos ayuda a confirmar que el proyecto está conectado correctamente. Crea main.py en la raíz del proyecto:

from tienda.carrito import Carrito
from tienda.precios import calcular_total
from tienda.productos import validar_producto


def main():
    producto = validar_producto("  teclado mecanico  ", 50000)

    carrito = Carrito()
    carrito.agregar(producto, cantidad=2)

    subtotal = carrito.subtotal()
    total = calcular_total(subtotal, descuento=10, impuesto=21)

    print("Producto:", producto["nombre"])
    print("Cantidad:", carrito.cantidad_total())
    print("Subtotal:", subtotal)
    print("Total:", total)


if __name__ == "__main__":
    main()
Código del archivo main.py del proyecto tienda-demo

2.10 Ejecutar el proyecto

Desde la raíz del proyecto, ejecuta:

python main.py

La salida debería ser similar a:

Producto: Teclado Mecanico
Cantidad: 2
Subtotal: 100000
Total: 108900.0
Ejecución del proyecto tienda-demo desde la terminal

Si aparece un error de importación, revisa que estés ubicado en la carpeta tienda-demo y que exista el archivo tienda/__init__.py.

2.11 Crear el archivo requirements.txt

Como este proyecto usará pytest, crea requirements.txt con este contenido:

pytest

En PowerShell puedes crearlo automáticamente con:

Set-Content -Path requirements.txt -Value "pytest"

Más adelante agregaremos herramientas como pytest-cov, pero por ahora alcanza con pytest.

2.12 Crear una primera prueba de humo

Una prueba de humo verifica algo mínimo para confirmar que el proyecto puede importarse y ejecutarse sin errores básicos. Dentro de tests, crea test_humo.py:

from tienda.productos import validar_producto


def test_se_puede_crear_un_producto_valido():
    producto = validar_producto("mouse", 12000)

    assert producto == {
        "nombre": "Mouse",
        "precio": 12000,
    }
Creación de la primera prueba de humo con pytest

No es una suite completa. Es solo una primera señal de que la estructura funciona y que pytest puede importar el paquete tienda.

2.13 Ejecutar la primera prueba

Desde la raíz del proyecto, ejecuta:

python -m pytest

La salida esperada será similar a:

Salida esperada al ejecutar la prueba de humo con pytest
collected 1 item

tests/test_humo.py .                                             [100%]

1 passed in 0.02s

Si la prueba pasa, ya tenemos un proyecto Python básico listo para empezar a escribir pruebas más específicas.

2.14 Estructura final del tema

Al finalizar, la estructura debería quedar así:

tienda-demo/
|-- .venv/
|-- main.py
|-- requirements.txt
|-- tienda/
|   |-- __init__.py
|   |-- carrito.py
|   |-- precios.py
|   `-- productos.py
`-- tests/
    `-- test_humo.py

2.15 Qué comportamientos podremos probar

Este proyecto fue elegido porque permite practicar varios tipos de pruebas sin agregar complejidad innecesaria:

  • Funciones puras: por ejemplo aplicar_descuento y calcular_impuesto.
  • Validaciones: nombres vacíos, precios inválidos, porcentajes fuera de rango.
  • Excepciones: errores esperados cuando los datos no cumplen las reglas.
  • Diccionarios: productos representados como datos simples.
  • Clases con estado: el carrito cambia cuando agregamos productos o lo vaciamos.
  • Organización de proyecto: código en un paquete y pruebas en una carpeta separada.

2.16 Diferencia entre código de producción y código de prueba

En este proyecto, los archivos de la carpeta tienda representan el código que queremos probar. Los archivos dentro de tests representan las pruebas.

Separar ambas cosas ayuda a mantener el proyecto ordenado. También evita que las pruebas se mezclen con la lógica principal de la aplicación.

Carpeta o archivo Responsabilidad
tienda/ Código principal del proyecto.
tests/ Pruebas automatizadas.
main.py Ejemplo de ejecución manual.
requirements.txt Dependencias necesarias para instalar el entorno.

2.17 Errores frecuentes

  • Olvidar __init__.py: puede causar problemas de importación según la estructura y la forma de ejecución.
  • Ejecutar pytest desde otra carpeta: las importaciones pueden fallar porque Python no encuentra el paquete tienda.
  • Guardar las pruebas dentro de la carpeta de código: funciona, pero en proyectos medianos suele ser menos claro.
  • No activar el entorno virtual: puede hacer que pytest no esté disponible.
  • Copiar código con indentación incorrecta: Python depende de la indentación para definir bloques.

2.18 Comandos usados en este tema

mkdir tienda-demo
cd tienda-demo
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install pytest
mkdir tienda
mkdir tests
New-Item tienda\__init__.py -ItemType File
python main.py
python -m pytest

En Linux o macOS, reemplaza la activación y la creación de __init__.py por:

source .venv/bin/activate
touch tienda/__init__.py

2.19 Qué debes recordar de este tema

  • Un buen proyecto de práctica debe tener código suficientemente variado para probar distintos comportamientos.
  • Separar código principal y pruebas mejora la organización.
  • La carpeta tests contendrá las pruebas automatizadas.
  • pytest puede descubrir pruebas dentro de archivos llamados test_*.py.
  • Antes de escribir muchas pruebas, conviene confirmar que el proyecto se puede importar y ejecutar.
  • Una prueba de humo ayuda a detectar problemas básicos de estructura.

2.20 Conclusión

En este tema creamos un proyecto Python de ejemplo para usar durante el curso. El proyecto incluye módulos con cálculos, validaciones, una clase con estado y una primera prueba de humo.

A partir del próximo tema trabajaremos con más detalle sobre entornos virtuales, instalación de dependencias y comandos de ejecución, para que cada prueba pueda repetirse de manera clara y confiable.