Ceci est la partie quatre sur cinq d'une série de tutoriels sur le test de code gourmand en données avec Go. Dans la troisième partie, j'ai couvert les tests sur une couche de données complexe locale comprenant une base de données relationnelle et un cache Redis..
Dans ce didacticiel, je vais passer en revue les tests effectués sur des magasins de données distants à l'aide de bases de données de test partagées, à l'aide d'instantanés de données de production et à la génération de vos propres données de test..
Jusqu'ici, tous nos tests ont été effectués localement. Parfois, cela ne suffit pas. Vous devrez peut-être tester des données difficiles à générer ou à obtenir localement. Les données de test peuvent être très volumineuses ou changer fréquemment (par exemple, instantané de données de production)..
Dans ces cas, il peut s'avérer trop lent et trop coûteux pour chaque développeur de copier les dernières données de test sur leur ordinateur. Parfois, les données de test sont sensibles et, en particulier, les développeurs distants ne devraient pas les avoir sur leur ordinateur portable..
Il y a plusieurs options à considérer ici. Vous pouvez utiliser une ou plusieurs de ces options dans différentes situations..
C'est une option très courante. Il existe une base de données de test partagée à laquelle tous les développeurs peuvent se connecter et tester. Cette base de test partagée est gérée en tant que ressource partagée et est souvent alimentée périodiquement avec des données de base. Les développeurs peuvent ensuite exécuter des tests pour interroger les données existantes. Ils peuvent également créer, mettre à jour et supprimer leurs propres données de test..
Dans ce cas, vous avez besoin de beaucoup de discipline et d'un bon processus en place. Si deux développeurs exécutent le même test en même temps, ce qui crée et supprime les mêmes objets, les deux tests échouent. Notez que même si vous êtes le seul développeur et que l'un de vos tests ne se nettoie pas correctement, votre prochain test peut échouer car la base de données contient à présent des données supplémentaires du test précédent qui pourraient interrompre votre test actuel..
C'est ainsi que fonctionnent les pipelines CI / CD ou même simplement les systèmes de construction automatisés. Un développeur valide une modification et une génération et un test automatisés commencent à s'exécuter. Mais vous pouvez aussi simplement vous connecter à une machine distante contenant votre code et y exécuter vos tests..
L'avantage est que vous pouvez répliquer la configuration locale exacte, tout en ayant accès aux données déjà disponibles dans l'environnement distant. L'inconvénient est que vous ne pouvez pas utiliser vos outils favoris pour le débogage.
Le lancement d'une instance de test ad-hoc distante garantit que vous êtes toujours isolé des autres développeurs. C'est assez similaire conceptuellement à l'exécution d'une instance locale. Vous devez toujours lancer votre magasin de données (ou vos magasins). Vous devez toujours les peupler (à distance). Cependant, votre code de test s'exécute localement et vous pouvez déboguer et dépanner en utilisant votre IDE préféré (Gogland dans mon cas). Il peut être difficile à gérer de manière opérationnelle si les développeurs continuent à exécuter les instances de test une fois les tests terminés..
Lorsque vous utilisez un magasin de données de test partagé, celui-ci est souvent rempli avec des instantanés de données de production. En fonction de la sensibilité et de la criticité des données, certains des avantages et inconvénients suivants peuvent être pertinents.
Avantages:
Les inconvénients:
D'ACCORD. Vous avez fait le saut et décidé d'utiliser un instantané des données de production. Si vos données impliquent des êtres humains sous quelque forme que ce soit, vous devrez peut-être anonymiser les données. C'est étonnamment difficile.
Vous ne pouvez pas simplement remplacer tous les noms et en finir. Il existe de nombreuses manières de récupérer des informations personnelles identifiables (PII) et des informations de santé protégées (PHI) à partir d’instantanés de données mal anonymisés. Consultez Wikipedia comme point de départ si vous êtes curieux.
Je travaille pour Helix, où nous développons une plateforme de génomique personnelle qui traite les données les plus privées, l’ADN séquencé des personnes. Nous disposons de sérieuses garanties contre les violations de données accidentelles (et malveillantes).
Lorsque vous utilisez des instantanés de données de production, vous devrez périodiquement actualiser vos instantanés et vos tests. Le timing vous appartient, mais faites-le à chaque changement de schéma ou de format.
Idéalement, vos tests ne devraient pas rechercher les propriétés d'un instantané particulier. Par exemple, si vous actualisez vos instantanés quotidiennement et que vous disposez d'un test qui vérifie le nombre d'enregistrements dans l'instantané, vous devrez le mettre à jour tous les jours. Il est bien mieux d'écrire vos tests de manière plus générique. Vous ne devez donc les mettre à jour que lorsque le code sous test change..
Une autre approche consiste à générer vos propres données de test. Les avantages et les inconvénients sont exactement les contraires de l’utilisation des instantanés de données de production. Notez que vous pouvez également combiner les deux approches et exécuter des tests sur des instantanés de données de production et d'autres tests à l'aide de données générées..
Comment feriez-vous pour générer vos données de test? Vous pouvez vous déchaîner et utiliser des données totalement aléatoires. Par exemple, pour Songify, nous pouvons simplement générer des chaînes totalement aléatoires pour les adresses de messagerie, les URL, les descriptions et les étiquettes des utilisateurs. Le résultat sera chaotique, mais des données valides étant donné que Songify n'effectue aucune validation des données..
Voici une fonction simple pour générer des chaînes aléatoires:
func makeRandomString (longueur int) chaîne const bytes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" randBytes: = make ([octet, longueur) pour i: = 0; je < length; i++ b := bytes[rand.Intn(len(bytes))] randBytes[i] = b return string(randBytes)
Écrivons une fonction qui ajoute cinq utilisateurs aléatoires, puis 100 chansons aléatoires réparties de manière aléatoire entre les cinq utilisateurs. Nous devons générer des utilisateurs car les chansons ne vivent pas dans le vide. Chaque chanson est toujours associée à au moins un utilisateur.
func (m * InMemoryDataLayer) PopulateWithRandomData () utilisateurs: = [] Utilisateur // Créez 5 utilisateurs pour i: = 0; je < 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)
Maintenant, nous pouvons écrire des tests qui exploitent beaucoup de données. Par exemple, voici un test qui vérifie que nous pouvons obtenir les 100 chansons en un seul appel. Notez que le test appelle PopulateWithRandomData ()
avant de faire l'appel.
func TestGetSongs (t * testing.T) dl, err: = NewInMemoryDataLayer () si err! = nil t.Error ("Échec de la création de la couche de données en mémoire") dl.PopulateWithRandomData () chansons, err: = dl.GetSongs () si err! = nil t.Error ("Echec de la création d'une couche de données en mémoire") si len (chansons)! = 100 t.Error ('GetSongs () n'a pas renvoyé le correct nombre de chansons ')
Habituellement, des données complètement aléatoires ne sont pas acceptables. Chaque magasin de données a des contraintes à respecter et des relations complexes à respecter pour créer des données valides sur lesquelles le système peut opérer. Vous pouvez également générer des données non valides pour tester la façon dont le système les gère, mais il s’agira d’erreurs spécifiques que vous allez injecter..
L'approche sera similaire à la génération de données aléatoires, sauf que vous aurez plus de logique pour appliquer les règles..
Par exemple, supposons que nous voulions appliquer la règle selon laquelle un utilisateur peut avoir au plus 30 chansons. Au lieu de créer au hasard 100 chansons et de les attribuer aux utilisateurs, nous pouvons décider que chaque utilisateur aura exactement 20 chansons, ou peut-être créer un utilisateur sans chanson et quatre autres utilisateurs avec 25 chansons chacun..
Dans certains cas, la génération de données de test est très compliquée. J'ai récemment travaillé sur un projet qui devait injecter des données de test à quatre micro-services différents, chacun gérant sa propre base de données avec les données de chaque base de données liées aux données d'autres bases de données. Il était assez difficile et fastidieux de tout synchroniser.
En règle générale, dans de telles situations, il est plus facile d’utiliser les API système et les outils existants qui créent des données au lieu d’aller directement dans plusieurs magasins de données et de prier pour que l’on ne déchire pas la structure de l’univers. Nous ne pouvions pas adopter cette approche car nous devions créer intentionnellement des données non valides pour tester diverses conditions d'erreur et éviter certains effets indésirables liés aux systèmes externes qui se produisent pendant le flux de travail normal..
Dans ce didacticiel, nous avons abordé les tests sur des magasins de données distants, l’utilisation de bases de données de test partagées, l’utilisation d’instantanés de données de production et la génération de vos propres données de test..
Dans la cinquième partie, nous nous concentrerons sur le test fuzz, le test de votre cache, l’intégrité des données, le test de l’impotence et les données manquantes. Restez à l'écoute.