Advertisement
  1. Code
  2. Go

Prueba de código intensivo de datos con Go, parte 1

Scroll to top
Read Time: 13 min
This post is part of a series called Testing Data-Intensive Code with Go.
Testing Data-Intensive Code With Go, Part 2

Spanish (Español) translation by steven (you can also view the original English article)

Visión general

Muchos sistemas no triviales también son intensivos en datos o están basados en datos. Probar las partes de los sistemas que requieren un uso intensivo de datos es muy diferente a probar sistemas que requieren un uso intensivo de código. Primero, puede haber mucha sofisticación en la propia capa de datos, como almacenes de datos híbridos, almacenamiento en caché, respaldo y redundancia.

Toda esta maquinaria no tiene nada que ver con la aplicación en sí, sino que tiene que ser probada. En segundo lugar, el código puede ser muy genérico y, para probarlo, es necesario generar datos estructurados de cierta manera. En esta serie de cinco tutoriales, abordaré todos estos aspectos, exploraré varias estrategias para diseñar sistemas de uso intensivo de datos comprobables con Go y profundizaré en ejemplos específicos.

En la primera parte, repasaré el diseño de una capa de datos abstractos que permite las pruebas adecuadas, cómo manejar errores en la capa de datos, cómo simular el código de acceso a datos y cómo probar contra una capa de datos abstractos.

Prueba contra una capa de datos

Tratar con almacenes de datos reales y sus complejidades es complicado y no está relacionado con la lógica empresarial. El concepto de una capa de datos te permite exponer una interfaz ordenada a tus datos y ocultar los detalles sangrientos de exactamente cómo se almacenan los datos y cómo acceder a ellos. Usaré una aplicación de muestra llamada "Songify" para la gestión personal de la música para ilustrar los conceptos con código real.

Diseñar una capa de datos abstracta

Revisemos el dominio de administración de música personal (los usuarios pueden agregar canciones y etiquetarlas) y consideremos qué datos necesitamos almacenar y cómo acceder a ellos. Los objetos de nuestro dominio son usuarios, canciones y etiquetas. Hay dos categorías de operaciones que deseas realizar en cualquier dato: consultas (solo lectura) y cambios de estado (crear, actualizar, eliminar). Aquí hay una interfaz básica para la capa de datos:

Ten en cuenta que el propósito de este modelo de dominio es presentar una capa de datos simple pero no completamente trivial para demostrar los aspectos de prueba. Obviamente, en una aplicación real habrá más objetos como álbumes, géneros, artistas y mucha más información sobre cada canción. Si las cosas se complican, siempre puedes almacenar información arbitraria sobre una canción en su descripción, así como adjuntar tantas etiquetas como desees.

En la práctica, es posible que desees dividir tu capa de datos en varias interfaces. Algunas de las estructuras pueden tener más atributos y los métodos pueden requerir más argumentos (por ejemplo, todos los métodos GetXXX() probablemente requerirán algunos argumentos de paginación). Es posible que necesites otras interfaces y métodos de acceso a datos para operaciones de mantenimiento como carga masiva, copias de seguridad y migraciones. A veces tiene sentido exponer una interfaz de acceso a datos asincrónica en lugar o además de la interfaz síncrona.

¿Qué obtuvimos de esta capa de datos abstractos?

  • Ventanilla única para operaciones de acceso a datos.
  • Visión clara de los requisitos de gestión de datos de nuestras aplicaciones en términos de dominio.
  • Capacidad para cambiar la implementación de la capa de datos concretos a voluntad.
  • Capacidad para desarrollar la capa lógica de dominio/negocio en una fase temprana contra la interfaz antes de que la capa de datos concreta esté completa o estable.
  • Por último, pero no menos importante, la capacidad de simular la capa de datos para realizar pruebas rápidas y flexibles del dominio/lógica empresarial.

Errores y manejo de errores en la capa de datos

Los datos pueden almacenarse en múltiples almacenes de datos distribuidos, en múltiples clústeres en diferentes ubicaciones geográficas en una combinación de centros de datos locales y la nube.

Habrá fallas y esas fallas deben manejarse. Idealmente, la lógica de manejo de errores (reintentos, tiempos de espera, notificación de fallas catastróficas) puede ser manejada por la capa de datos concretos. El código lógico del dominio debería simplemente recuperar los datos o un error genérico cuando los datos son inalcanzables.

En algunos casos, la lógica del dominio puede querer un acceso más granular a los datos y seleccionar una estrategia de respaldo en ciertas situaciones (por ejemplo, solo hay datos parciales disponibles porque parte del clúster es inaccesible o los datos están obsoletos porque la caché no se actualizó ). Esos aspectos tienen implicaciones para el diseño de su capa de datos y para su prueba.

En lo que respecta a las pruebas, debes devolver tus propios errores definidos en la capa de datos abstractos y asignar todos los mensajes de error concretos a tus propios tipos de error o confiar en mensajes de error muy genéricos.

Código de acceso a datos simulado

Vamos a burlarnos de nuestra capa de datos. El propósito del simulacro es reemplazar la capa de datos reales durante las pruebas. Eso requiere que la capa de datos simulada exponga la misma interfaz y pueda responder a cada secuencia de métodos con una respuesta enlatada (o calculada).

Además, es útil realizar un seguimiento de cuántas veces se llamó a cada método. No lo demostraré aquí, pero incluso es posible realizar un seguimiento del orden de las llamadas a diferentes métodos y qué argumentos se pasaron a cada método para garantizar una determinada cadena de llamadas.

Aquí está la estructura de la capa de datos simulada.

La declaración const enumera todas las operaciones admitidas y los errores. Cada operación tiene su propio índice en el segmento de Índices. El índice de cada operación representa cuántas veces se llamó al método correspondiente, así como cuál debería ser la siguiente respuesta y error.

Para cada método que tiene un valor de retorno además de un error, hay una porción de respuestas. Cuando se llama al método simulado, se devuelven la respuesta y el error correspondientes (basados en el índice de este método). Para los métodos que no tienen un valor de retorno excepto un error, no es necesario definir un segmento XXXResponses.

Ten en cuenta que todos los métodos comparten los errores. Eso significa que si deseas probar una secuencia de llamadas, deberás inyectar la cantidad correcta de errores en el orden correcto. Un diseño alternativo usaría para cada respuesta un par consistente en el valor de retorno y el error. La función NewMockDataLayer() devuelve una nueva estructura de capa de datos simulada con todos los índices inicializados a cero.

Aquí está la implementación del método GetUsers(), que ilustra estos conceptos.

La primera línea obtiene el índice actual de la operación GET_USERS (inicialmente será 0).

La segunda línea obtiene la respuesta del índice actual.

Las líneas tercera a quinta asignan el error del índice actual si se completó el campo Errors e incrementan el índice de errores. Al probar el camino feliz, el error será nulo. Para que sea más fácil de usar, puedes evitar inicializar el campo Errors y luego cada método devolverá nulo para el error.

La siguiente línea incrementa el índice, por lo que la siguiente llamada obtendrá la respuesta adecuada.

La última línea simplemente regresa. Los valores devueltos con nombre para los usuarios y err ya están llenos (o null por defecto para err).

Aquí hay otro método, GetLabels(), que sigue el mismo patrón. La única diferencia es qué índice se usa y qué colección de respuestas predefinidas se usa.

Este es un excelente ejemplo de un caso de uso en el que los genéricos podrían ahorrar una gran cantidad de código repetitivo. Es posible aprovechar la reflexión con el mismo efecto, pero está fuera del alcance de este tutorial. La principal conclusión aquí es que la capa de datos simulada puede seguir un patrón de propósito general y admitir cualquier escenario de prueba, como verás pronto.

¿Qué tal algunos métodos que solo devuelven un error? Consulta el método CreateUser(). Es incluso más simple porque solo se ocupa de los errores y no necesita administrar las respuestas predefinidas.

Esta capa de datos simulada es solo un ejemplo de lo que se necesita para simular una interfaz y proporcionar algunos servicios útiles para probar. Puedes crear tu propia implementación simulada o utilizar las bibliotecas simuladas disponibles. Incluso hay un framework estándar llamado GoMock.

Personalmente, encuentro que los frameworks simulados son fáciles de implementar y prefiero implementar los míos (a menudo generándolos automáticamente) porque paso la mayor parte de mi tiempo de desarrollo escribiendo pruebas y burlándome de las dependencias. YMMV.

Prueba contra una capa de datos abstracta

Ahora que tenemos una capa de datos simulada, escribamos algunas pruebas contra ella. Es importante darse cuenta de que aquí no probamos la capa de datos en sí. Probaremos la propia capa de datos con otros métodos más adelante en esta serie. El propósito aquí es probar la lógica del código que depende de la capa de datos abstractos.

Por ejemplo, supongamos que un usuario quiere agregar una canción, pero tenemos una cuota de 100 canciones por usuario. El comportamiento esperado es que si el usuario tiene menos de 100 canciones y la canción agregada es nueva, se agregará. Si la canción ya existe, devuelve un error de "Canción duplicada". Si el usuario ya tiene 100 canciones, devuelve un error de "Cuota de canciones excedida".

Escribamos una prueba para estos casos de prueba usando nuestra capa de datos simulada. Esta es una prueba de caja blanca, lo que significa que necesitas saber a qué métodos de la capa de datos llamará el código bajo prueba y en qué orden para que puedas completar las respuestas simuladas y los errores correctamente. Entonces, el enfoque de prueba primero no es ideal aquí. Primero escribamos el código.

Aquí está la estructura SongManager. Depende solo de la capa de datos abstractos. Eso te permitirá pasarle una implementación de una capa de datos real en producción, pero una capa de datos simulada durante la prueba.

El propio SongManager es completamente independiente de la implementación concreta de la interfaz DataLayer. La estructura SongManager también acepta un usuario, que almacena. Presumiblemente, cada usuario activo tiene su propia instancia de SongManager, y los usuarios solo pueden agregar canciones para ellos mismos. La función NewSongManager() asegura que la interfaz DataLayer de entrada no sea nula.

Implementemos un método AddSong(). El método llama primero a GetSongsByUser() de la capa de datos y luego pasa por varias verificaciones. Si todo está bien, llama al método AddSong() de la capa de datos y devuelve el resultado.

Al observar este código, puedes ver que hay otros dos casos de prueba que descuidamos: las llamadas a los métodos de la capa de datos GetSongByUser() y AddSong() pueden fallar por otras razones. Ahora, con la implementación de SongManager.AddSong() frente a nosotros, podemos escribir una prueba completa que cubra todos los casos de uso. Empecemos por el camino feliz. El método TestAddSong_Success() crea un usuario llamado Gigi y una capa de datos simulada.

Completa el campo GetSongsByUserResponses con un segmento que contiene un segmento vacío, lo que dará como resultado un segmento vacío cuando SongManager llame a GetSongsByUser() en la capa de datos simulada sin error. No es necesario hacer nada para la llamada al método AddSong() de la capa de datos simulada, que devolverá un error nulo por defecto. La prueba solo verifica que de hecho no se devolvió ningún error de la llamada principal al método AddSong() de SongManager.

Probar las condiciones de error también es muy fácil. Tienes control total sobre lo que devuelve la capa de datos de las llamadas a GetSongsByUser() y AddSong(). Aquí hay una prueba para verificar que al agregar una canción duplicada obtiene el mensaje de error correcto.

Los siguientes dos casos de prueba prueban que se devuelve el mensaje de error correcto cuando falla la capa de datos. En el primer caso, GetSongsByUser() de la capa de datos devuelve un error.

En el segundo caso, el método AddSong() de la capa de datos devuelve un error. Dado que la primera llamada a GetSongsByUser() debería tener éxito, el segmento mock.Errors contiene dos elementos: null para la primera llamada y el error para la segunda llamada.

Conclusión

En este tutorial, presentamos el concepto de una capa de datos abstracta. Luego, utilizando el dominio de administración de música personal, demostramos cómo diseñar una capa de datos, crear una capa de datos simulada y usar la capa de datos simulada para probar la aplicación.

En la segunda parte, nos centraremos en las pruebas utilizando una capa de datos en memoria real. Mantente al tanto.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.