Esta es la parte cuatro de cinco en una serie de tutoriales sobre pruebas de código de uso intensivo de datos con Go. En la tercera parte, cubrí las pruebas con una capa de datos complejos locales que incluye una base de datos relacional y un caché Redis.
En este tutorial, analizaré los almacenes de datos remotos utilizando bases de datos de prueba compartidas, usando instantáneas de datos de producción y generando sus propios datos de prueba.
Hasta ahora, todas nuestras pruebas se realizaron localmente. A veces, eso no es suficiente. Es posible que deba hacer pruebas contra datos que son difíciles de generar u obtener localmente. Los datos de prueba pueden ser muy grandes o cambiar con frecuencia (por ejemplo, instantánea de datos de producción).
En estos casos, puede ser demasiado lento y costoso para cada desarrollador copiar los últimos datos de prueba en su máquina. A veces, los datos de prueba son confidenciales, y especialmente los desarrolladores remotos no deberían tenerlos en su computadora portátil.
Hay varias opciones aquí para considerar. Puede usar una o más de estas opciones en diferentes situaciones.
Esta es una opción muy común. Hay una base de datos de prueba compartida a la que todos los desarrolladores pueden conectarse y probar. Esta base de datos de prueba compartida se administra como un recurso compartido y, a menudo, se rellena periódicamente con algunos datos de referencia, y luego los desarrolladores pueden ejecutar pruebas contra él que consulten los datos existentes. También pueden crear, actualizar y eliminar sus propios datos de prueba..
En este caso, necesita mucha disciplina y un buen proceso en el lugar. Si dos desarrolladores ejecutan la misma prueba al mismo tiempo que crea y elimina los mismos objetos, ambas pruebas fallarán. Tenga en cuenta que incluso si usted es el único desarrollador y una de sus pruebas no se limpia correctamente, su próxima prueba podría fallar porque la base de datos ahora tiene algunos datos adicionales de la prueba anterior que pueden interrumpir su prueba actual.
Así es como funcionan las tuberías de CI / CD o incluso los sistemas de compilación automatizados. Un desarrollador confirma un cambio, y una compilación y prueba automatizadas comienzan a ejecutarse. Pero también puede simplemente conectarse a una máquina remota que tenga su código y ejecutar sus pruebas allí..
El beneficio es que puede replicar la configuración local exacta, pero tiene acceso a los datos que ya están disponibles en el entorno remoto. El inconveniente es que no puedes usar tus herramientas favoritas para depurar.
El lanzamiento de una instancia de prueba ad-hoc remota garantiza que aún esté aislado de otros desarrolladores. Conceptualmente es bastante similar a la ejecución de una instancia local. Aún necesita iniciar su almacén de datos (o tiendas). Todavía necesita rellenarlos (de forma remota). Sin embargo, su código de prueba se ejecuta localmente, y puede depurar y solucionar problemas utilizando su IDE favorito (Gogland en mi caso). Puede ser difícil de administrar operativamente si los desarrolladores mantienen las instancias de prueba en ejecución después de que se realizan las pruebas.
Cuando se utiliza un almacén de datos de prueba compartido, a menudo se rellena con instantáneas de datos de producción. Dependiendo de cuán sensibles y críticos sean los datos, algunos de los siguientes pros y contras pueden ser relevantes.
Pros:
Contras:
DE ACUERDO. Ha dado el salto y ha decidido utilizar una instantánea de datos de producción. Si sus datos involucran seres humanos en cualquier forma o forma, es posible que deba anonimizar los datos. Esto es sorprendentemente difícil..
No puedes simplemente reemplazar todos los nombres y terminar con eso. Hay muchas maneras de recuperar la PII (información de identificación personal) y la PHI (información de salud protegida) de las instantáneas de datos mal anonimizados. Echa un vistazo a Wikipedia como punto de partida si tienes curiosidad..
Trabajo para Helix, donde desarrollamos una plataforma de genómica personal que trata con los datos más privados: el ADN secuenciado de las personas. Tenemos algunas garantías serias contra las violaciones de datos accidentales (y maliciosas)..
Cuando utilice instantáneas de datos de producción, deberá actualizar periódicamente sus instantáneas y, en consecuencia, sus pruebas. El tiempo depende de usted, pero definitivamente hágalo siempre que haya un cambio de esquema o de formato.
Idealmente, sus pruebas no deberían probar las propiedades de una instantánea en particular. Por ejemplo, si actualiza sus instantáneas diariamente y tiene una prueba que verifica el número de registros en la instantánea, entonces tendrá que actualizar esta prueba todos los días. Es mucho mejor escribir sus pruebas de una manera más genérica, por lo que necesita actualizarlas solo cuando el código bajo prueba cambie.
Otro enfoque es generar tus propios datos de prueba. Los pros y los contras son los opuestos exactos del uso de instantáneas de datos de producción. Tenga en cuenta que también puede combinar los dos enfoques y ejecutar algunas pruebas en instantáneas de datos de producción y otras pruebas utilizando datos generados.
¿Cómo harías para generar tus datos de prueba? Puedes ir salvaje y usar datos totalmente aleatorios. Por ejemplo, para Songify podemos generar cadenas totalmente aleatorias para el correo electrónico, la URL, la descripción y las etiquetas del usuario. El resultado será caótico, pero los datos válidos ya que Songify no hace ninguna validación de datos.
Aquí hay una función simple para generar cadenas aleatorias:
func makeRandomString (length int) string const bytes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" randBytes: = make ([byte, length) for i: = 0; yo < length; i++ b := bytes[rand.Intn(len(bytes))] randBytes[i] = b return string(randBytes)
Escribamos una función que agregue cinco usuarios aleatorios y luego agregue 100 canciones aleatorias distribuidas al azar entre los cinco usuarios. Debemos generar usuarios porque las canciones no viven en un vacío. Cada canción siempre está asociada con al menos un usuario..
func (m * InMemoryDataLayer) PopulateWithRandomData () users: = [] User // Crea 5 usuarios para i: = 0; yo < 5; i++ name := makeRandomString(15) u := User Email: name + "@" + makeRandomString(12) + ".com", Name: makeRandomString(17), m.CreateUser(u) users = append(users, u) // Create 100 songs and associate randomly with // one of the 5 users for i := 0; i < 100; i++ user := users[rand.Intn(len(users))] song := Song Url: fmt.Sprintf("http://www.%s.com", makeRandomString(13)), Name: makeRandomString(16), m.AddSong(user, song, []Label)
Ahora, podemos escribir algunas pruebas que operan una gran cantidad de datos. Por ejemplo, aquí hay una prueba que verifica que podemos obtener las 100 canciones en una llamada. Tenga en cuenta que la prueba llama PopulateWithRandomData ()
antes de hacer la llamada.
func TestGetSongs (t * testing.T) dl, err: = NewInMemoryDataLayer () if err! = nil t.Error ("Error al crear la capa de datos en la memoria") dl.PopulateWithRandomData () songs, err: = dl.GetSongs () si err! = nil t.Error ("Error al crear la capa de datos en la memoria") si len (canciones)! = 100 t.Error ('GetSongs () no devolvió el correcto número de canciones ')
Por lo general, los datos completamente aleatorios no son aceptables. Cada almacén de datos tiene restricciones que debe respetar y relaciones complejas que deben seguirse para crear datos válidos en los que el sistema pueda operar. Es posible que desee generar algunos datos no válidos también para probar cómo el sistema los maneja, pero esos serán errores específicos que inyectará.
El enfoque será similar a la generación de datos aleatorios, excepto que tendrá más lógica para hacer cumplir las reglas..
Por ejemplo, digamos que queremos imponer la regla de que un usuario puede tener como máximo 30 canciones. En lugar de crear aleatoriamente 100 canciones y asignarlas a los usuarios, podemos decidir que cada usuario tendrá exactamente 20 canciones, o tal vez crear un usuario sin canciones y otros cuatro usuarios con 25 canciones cada uno..
En algunos casos, generar datos de prueba es muy complicado. Recientemente trabajé en un proyecto que tuvo que inyectar datos de prueba a cuatro microservicios diferentes, cada uno de los cuales gestionaba su propia base de datos con los datos de cada base de datos relacionados con los datos de otras bases de datos. Fue bastante desafiante y laborioso mantener todo en sincronía.
Por lo general, en tales situaciones, es más fácil utilizar las API de los sistemas y las herramientas existentes que crean datos en lugar de ir directamente a múltiples almacenes de datos y rezar para que no rasgue la trama del universo. No pudimos adoptar este enfoque porque en realidad necesitábamos crear algunos datos no válidos intencionalmente para probar diversas condiciones de error y omitir algunos efectos secundarios relacionados con los sistemas externos que ocurren durante el flujo de trabajo normal.
En este tutorial, cubrimos las pruebas con los almacenes de datos remotos, utilizando bases de datos de prueba compartidas, usando instantáneas de datos de producción y generando sus propios datos de prueba.
En la parte cinco nos centraremos en las pruebas fuzz, las pruebas de su caché, las pruebas de integridad de los datos, las pruebas de idempotencia y los datos faltantes. Manténganse al tanto.