15. Transacciones, persistencia y consistencia de datos

15.1 Introducción

Cuando una integración modifica datos, no alcanza con verificar que una operación responda correctamente. También debemos comprobar qué estado queda guardado y si ese estado es consistente.

Muchas funcionalidades realizan varias operaciones relacionadas: crear una orden, descontar stock, registrar un pago, actualizar un estado o publicar un evento. Si una parte falla y otra queda guardada, el sistema puede terminar con datos incoherentes.

En este tema estudiaremos transacciones, persistencia y consistencia de datos desde la perspectiva de las pruebas de integración.

15.2 Qué es persistencia

La persistencia es la capacidad de guardar información para que permanezca disponible después de ejecutar una operación. En aplicaciones reales, suele estar asociada a bases de datos, archivos o almacenamiento externo.

En pruebas de integración, verificar persistencia significa comprobar que:

  • Los datos se guardan donde corresponde.
  • Los valores guardados son correctos.
  • Las relaciones entre datos quedan completas.
  • La información puede consultarse después.
  • Los cambios no afectan registros que no debían modificarse.
Una respuesta exitosa no garantiza persistencia correcta. Siempre que el estado guardado sea importante, debe verificarse.

15.3 Qué es una transacción

Una transacción agrupa varias operaciones para que se confirmen o se reviertan como una unidad. Si todo sale bien, se realiza un commit. Si algo falla, se realiza un rollback para deshacer los cambios.

Por ejemplo, al confirmar una compra puede ser necesario:

  • Crear la orden.
  • Guardar los ítems.
  • Descontar stock.
  • Registrar el pago.

Si el descuento de stock falla, puede ser incorrecto dejar la orden creada como si todo hubiera terminado bien. La transacción ayuda a mantener la consistencia.

15.4 Propiedades esperadas de una transacción

En bases de datos suele hablarse de propiedades ACID. Para un curso inicial, podemos resumirlas así:

Propiedad Idea principal Pregunta para probar
Atomicidad Todo se completa o todo se revierte. ¿Quedan cambios parciales si algo falla?
Consistencia Los datos quedan en un estado válido. ¿Se respetan reglas y relaciones?
Aislamiento Operaciones simultáneas no deberían interferirse incorrectamente. ¿Dos operaciones concurrentes pisan datos?
Durabilidad Lo confirmado permanece guardado. ¿El dato sigue disponible después del commit?

15.5 Consistencia de datos

La consistencia significa que los datos guardados respetan las reglas del sistema. No alcanza con que existan registros; deben formar un estado válido.

Ejemplos de consistencia:

  • Una orden pagada debe tener un pago asociado.
  • Un stock no debería quedar negativo si la regla lo prohíbe.
  • Una factura debe estar asociada a un cliente existente.
  • Un usuario eliminado no debería aparecer como activo.
  • El total de una orden debe coincidir con sus ítems.

Las pruebas de integración deben observar estas relaciones cuando la operación afecta más de un dato.

15.6 Verificar el estado final

Una prueba de integración debe verificar el estado final producido por la operación. Esto puede incluir consultas a la base, lectura de archivos, inspección de mensajes o revisión de estados en servicios simulados.

Al verificar el estado final, conviene preguntar:

  • ¿Se creó lo que debía crearse?
  • ¿Se actualizó lo que debía actualizarse?
  • ¿No se modificó lo que debía permanecer igual?
  • ¿Las relaciones quedaron correctas?
  • ¿Los estados son coherentes con el resultado de la operación?

Una prueba que solo mira la respuesta puede pasar aunque el sistema haya guardado datos incorrectos.

15.7 Probar commit

Probar el commit significa verificar que una operación exitosa confirme todos los cambios esperados.

Por ejemplo, al confirmar una compra con pago aprobado, la prueba podría verificar:

  • La orden existe.
  • Los ítems fueron guardados.
  • El stock se redujo.
  • El pago quedó registrado.
  • El estado de la orden es correcto.

El objetivo es comprobar que el flujo exitoso deja un estado completo y usable por otros componentes.

15.8 Probar rollback

Probar rollback significa verificar que una operación fallida no deje cambios parciales indebidos.

Supongamos que una compra falla al registrar el pago. Según la regla del sistema, puede esperarse que:

  • No se cree la orden.
  • O se cree en estado pendiente, pero sin descontar stock.
  • No se registre un pago aprobado.
  • No se publique un evento de compra confirmada.
  • Quede evidencia del error si el sistema debe registrarlo.

Lo importante es que el estado final sea el definido por el negocio, no un resultado accidental.

15.9 Estados intermedios

Algunas operaciones pasan por estados intermedios. Esto no siempre es incorrecto. Lo importante es que esos estados estén definidos y no queden abandonados por error.

Ejemplos:

  • Orden pendiente de pago.
  • Pago en revisión.
  • Archivo importado parcialmente.
  • Mensaje pendiente de procesamiento.

Una prueba de integración debe distinguir entre un estado intermedio válido y un estado inconsistente causado por una falla.

15.10 Operaciones idempotentes

Una operación es idempotente cuando repetirla produce el mismo resultado final sin duplicar efectos no deseados. Este concepto es muy importante en integraciones con reintentos.

Por ejemplo, si se reintenta la confirmación de un pago por un error de red, el sistema no debería crear dos órdenes ni descontar stock dos veces.

Una prueba de integración puede verificar que:

  • Repetir la misma solicitud no genera duplicados.
  • El estado final sigue siendo coherente.
  • Los eventos no se publican más veces de las permitidas.
  • Los identificadores de operación se usan correctamente.

15.11 Concurrencia y consistencia

La concurrencia aparece cuando varias operaciones intentan modificar datos relacionados al mismo tiempo. En integración, esto puede generar problemas de consistencia.

Ejemplos:

  • Dos compras intentan usar la última unidad de stock.
  • Dos usuarios modifican el mismo registro.
  • Dos procesos consumen el mismo mensaje.
  • Un reporte lee datos mientras una operación todavía está escribiendo.

No todas las suites de integración cubren concurrencia en profundidad, pero los casos críticos deben considerarla cuando el riesgo existe.

15.12 Consistencia eventual

En sistemas con eventos o procesos asíncronos, no siempre todos los datos se actualizan al mismo tiempo. Puede existir consistencia eventual: el sistema tarda un tiempo en llegar al estado final esperado.

Por ejemplo, una orden puede crearse inmediatamente, pero el correo de confirmación o la actualización de un reporte pueden ocurrir segundos después.

Al probar estos casos, conviene:

  • Esperar de forma controlada hasta que aparezca el resultado.
  • Usar identificadores para localizar el evento correcto.
  • Definir un tiempo máximo razonable.
  • Evitar pausas fijas innecesarias.
  • Distinguir entre demora esperada y falla real.

15.13 Verificar lo que no debe ocurrir

En consistencia de datos, también es importante verificar efectos que no deben ocurrir. A veces una operación parece correcta porque produce una respuesta válida, pero genera efectos secundarios indebidos.

Ejemplos:

  • No debe crearse una orden si el pago fue rechazado.
  • No debe descontarse stock si la compra fue cancelada.
  • No debe enviarse notificación si la operación falló.
  • No deben modificarse registros de otro usuario.
  • No debe duplicarse un registro al reintentar.

Estas verificaciones son esenciales para detectar errores de integración que dejan datos incorrectos sin generar una excepción visible.

15.14 Ejemplo: compra con pago rechazado

Supongamos una prueba de integración para una compra cuyo pago es rechazado.

Verificación Resultado esperado
Respuesta al usuario La operación informa que el pago fue rechazado.
Orden No se crea, o queda en estado rechazado según la regla definida.
Stock No se descuenta.
Pago No queda registrado como aprobado.
Eventos No se publica evento de compra confirmada.

Esta prueba no se limita a verificar el error. También comprueba que el estado del sistema no quede contaminado por una operación fallida.

15.15 Errores comunes

Al probar transacciones y consistencia, suelen aparecer errores como:

  • Verificar solo la respuesta y no el estado guardado.
  • No probar qué ocurre cuando falla una operación intermedia.
  • Dejar cambios parciales después de un error.
  • No considerar reintentos o duplicados.
  • Confundir estados intermedios válidos con inconsistencias.
  • Usar datos de prueba que no revelan problemas de relación.
  • No limpiar datos creados por pruebas fallidas.

15.16 Lista de verificación

Para evaluar una prueba de integración relacionada con persistencia, podemos preguntar:

  • ¿La prueba verifica el estado final guardado?
  • ¿Comprueba que los datos relacionados sean coherentes?
  • ¿Incluye al menos un caso de error relevante?
  • ¿Verifica que no queden cambios parciales indebidos?
  • ¿Considera reintentos o duplicados si el flujo los permite?
  • ¿Distingue estados intermedios válidos de estados inconsistentes?
  • ¿Limpia los datos creados durante la ejecución?

15.17 Qué debes recordar de este tema

  • La persistencia debe verificarse observando el estado guardado, no solo la respuesta.
  • Una transacción agrupa cambios para confirmarlos o revertirlos como una unidad.
  • La consistencia significa que los datos quedan en un estado válido según las reglas del sistema.
  • Los casos de error son fundamentales para comprobar rollback o estados alternativos correctos.
  • Las operaciones con reintentos deben evitar duplicados y efectos repetidos no deseados.
  • En procesos asíncronos puede existir consistencia eventual, que debe probarse con esperas controladas.

15.18 Conclusión

Las pruebas de integración deben demostrar que los cambios de datos ocurren de manera correcta, completa y consistente. Esto incluye caminos exitosos, fallas intermedias, reintentos y estados finales.

Cuando una operación modifica varias partes del sistema, una prueba útil no solo pregunta si la operación respondió bien. Pregunta qué quedó guardado, qué no debía cambiar y si el sistema puede continuar funcionando desde ese estado.

En el próximo tema veremos la integración entre capas de una aplicación, como controladores, servicios y repositorios.