DMarlon Studio

Lección 20 · 20 de 20

Proyecto: un inventario con pruebas · Python

Un pequeño inventario en Python que reúne clases, dataclasses y excepciones propias, comprobado con pruebas assert. Ejecútelo en DMarlon Studio.

Python · Proyecto: un inventario con pruebas en DMarlon Studio: el código del ejemplo y su salida en la terminal

Este proyecto junta lo visto en el curso. Product es una clase de datos: tres campos, uno con valor por defecto, y un __repr__ gratis que se ve en la salida. OutOfStock es una excepción propia para el único error que el inventario sabe tratar.

Inventory es una clase normal. Su __init__ recibe cualquier número de productos con *products y los guarda en un diccionario por código (sku) con una comprensión. sell() busca el producto (si el código no existe salta un KeyError, un error distinto a propósito), comprueba las existencias, las descuenta y devuelve el importe. value() suma el valor de todo el almacén.

La segunda mitad son pruebas. `assert condición` no hace nada si la condición es cierta y lanza AssertionError si es falsa, con el mensaje opcional que va tras la coma. Cada assert fija un comportamiento: el valor inicial es 955, vender 4 sillas cobra 198 y quedan 6.

Para probar que algo falla, se intenta dentro de un try: si sell("TABLE", 5) no lanzara OutOfStock, se llegaría a assert False y la prueba fallaría. Como sí la lanza, el except la captura y muestra el mensaje. Si el programa llega a All tests passed, todas las comprobaciones se han cumplido.

En proyectos reales estas pruebas van en funciones test_... y las ejecuta pytest, que usa estos mismos assert. Ojo: python -O desactiva los assert, así que no los use para validar datos en producción.

Código · main.py

from dataclasses import dataclassclass OutOfStock(Exception): pass     # our own error for this program@dataclassclass Product:    sku: str    price: float    stock: int = 0class Inventory:    def __init__(self, *products):        self.products = {p.sku: p for p in products}    def sell(self, sku, qty):        product = self.products[sku]          # KeyError if the SKU is unknown        if qty > product.stock:            raise OutOfStock(f"only {product.stock} x {sku} left")        product.stock -= qty        return qty * product.price    def value(self):        return sum(p.price * p.stock for p in self.products.values())shop = Inventory(Product("CHAIR", 49.5, 10), Product("TABLE", 230.0, 2))assert shop.value() == 955.0                  # a test: stops here if Falseassert shop.sell("CHAIR", 4) == 198.0assert shop.products["CHAIR"].stock == 6, "stock was not updated"try:    shop.sell("TABLE", 5)    assert False, "OutOfStock expected"except OutOfStock as error:    print("Expected error:", error)print(shop.products["CHAIR"], "| value:", shop.value())print("All tests passed")

Salida · python3 main.py

Expected error: only 2 x TABLE left
Product(sku='CHAIR', price=49.5, stock=6) | value: 757.0
All tests passed

Para recordar

  • Una clase de datos para los datos y una clase normal para el comportamiento.
  • Excepciones propias para los errores que el programa sabe tratar.
  • assert condición, "mensaje" detiene el programa si algo no cuadra.
  • Para probar un error: try, la llamada, assert False y except.
  • pytest ejecuta funciones test_... con estos mismos assert.

Ejercicio

Añada un método restock(sku, qty) que rechace cantidades negativas con ValueError, y escriba dos pruebas: una que reponga 3 mesas y otra que compruebe el error.