TESTS PARA REPOSITORIOS SQLITE¶
- Módulo: PD4 - CEPY
- Unidad: UT3 — Persistencia con bases de datos
- Prerrequisitos: apuntes UT3-1, UT3-2, unittest básico,
pathlib
1. Aislamiento en los tests de base de datos¶
Por qué los tests necesitan su propia base de datos¶
Un test de repositorio comprueba que las operaciones sobre la base de datos (guardar, obtener, etc.) se comportan correctamente. A diferencia de los tests de dominio — que trabajan con objetos en memoria —, estos tests leen y escriben en un fichero .db real.
El problema surge cuando varios tests comparten la misma base de datos: los datos que crea un test quedan visibles para el siguiente, y el resultado puede depender del orden de ejecución. Esto hace los tests frágiles e impredecibles.
La solución es que cada test trabaje sobre una base de datos nueva y vacía: creada justo antes de que el test empiece y eliminada al terminar. Para ello se usan los métodos setUp y tearDown de unittest.TestCase.
El fichero de test con Path¶
pathlib.Path gestiona la ruta del fichero de test y proporciona los métodos .exists() (comprobar que archivo existe) y .unlink() (eliminar archivo) que usaremos en el ciclo setUp/tearDown.
BD_TEST es un atributo de clase — todas las instancias del test comparten la misma ruta, pero cada test crea y elimina el fichero de nuevo.
Usa un nombre distinto al de la base de datos de producción (
expendedora.db) para que los tests nunca modifiquen los datos reales.
Mala práctica: usar la BD de producción en los tests
2. El ciclo setUp y tearDown¶
setUp — preparar la base de datos antes de cada test¶
setUp es el método de unittest.TestCase que se ejecuta automáticamente antes de cada método de test. Aquí creamos el esquema de tablas y el repositorio que usará el test.
sqlite3.connect() acepta objetos Path directamente — no hace falta convertir a str. El unlink() inicial garantiza un estado de partida limpio aunque el proceso anterior se interrumpiera sin llegar a tearDown.
tearDown — eliminar el fichero al terminar¶
tearDown se ejecuta automáticamente después de cada método de test, tanto si el test pasa como si falla. Su única responsabilidad es dejar el sistema como estaba antes del test.
En Python 3.8+ se puede escribir
self.BD_TEST.unlink(missing_ok=True)para omitir la comprobación de existencia previa. En versiones anteriores la comprobación con.exists()es obligatoria.
setUp y tearDown forman un ciclo simétrico: si setUp crea, tearDown destruye. Cada test recibe una base de datos vacía y la deja igual al terminar.
Mala práctica: no limpiar tras el test
3. Testar las operaciones del repositorio¶
Verificar que guardar() persiste los datos¶
El test comprueba el resultado observable: después de llamar a guardar(), los datos deben poderse recuperar con obtener().
Verificamos atributos individuales en lugar del objeto completo porque Item no tiene __eq__ definido. Comparar objetos enteros produciría siempre False.
Verificar que obtener() reconstruye el tipo correcto¶
El repositorio debe devolver un ItemConDescuento cuando el producto tiene entrada en descuentos, y un Item base cuando no la tiene.
assertIsInstancecomprueba el tipo del objeto, no solo sus atributos. Es el test adecuado cuando la lógica depende de que el objeto sea de una clase concreta.El lab UT3-A3 guía la construcción del fichero
tests/test_repositorio_sqlite.pycompleto, paso a paso, aplicando todos los patrones de esta sección.
4. Testar excepciones de dominio¶
assertRaises con excepciones propias¶
Los tests verifican que el repositorio lanza excepciones de dominio, no excepciones internas de sqlite3. El patrón es idéntico al que ya conoces con ValueError.
Testar
ProductoYaExisteErroren lugar desqlite3.IntegrityErrorgarantiza que el repositorio cumple su contrato: la capa de presentación nunca debería ver excepciones de SQLite.
Verificar el mensaje de la excepción¶
Si el mensaje de error aporta información útil al usuario, se puede comprobar con assertIn sobre el contexto del assertRaises:
ctx.exception contiene el objeto excepción capturado. str() lo convierte a texto para poder buscar dentro del mensaje con assertIn.