Limitations qui doivent être violées ou comment nous avons accéléré trois fois les tests fonctionnels

image

Les tests fonctionnels sont une chose utile. Au début, cela ne prend pas beaucoup de temps, mais le projet se développe et de plus en plus de tests sont nécessaires. Nous n'avions pas l'intention de tolérer un ralentissement de la vitesse de livraison et, rassemblant nos forces, nous avons accéléré les tests fonctionnels à trois reprises. Dans l'article, vous trouverez des conseils universels, cependant, vous remarquerez un effet spécial sur les grands projets.

En bref sur l'application


Mon équipe développe une API publique qui fournit des données aux utilisateurs de 2GIS. Lorsque vous allez sur 2gis.ru et cherchez "Supermarchés", vous obtenez une liste d'organisations - ce sont les données de notre API. Sur nos 2000+ RPS, presque tous les problèmes deviennent critiques si une sorte de fonctionnalité tombe en panne.

L'application est écrite en Scala, les tests sont écrits en PHP, la base de données est PostgreSQL-9.4. Nous avons environ 25 000 tests fonctionnels, ils prennent 30 minutes à effectuer sur une machine virtuelle dédiée pour une régression générale. La durée des tests ne nous a pas beaucoup dérangés - nous étions habitués au fait que les tests pouvaient prendre 60 minutes sur l'ancien framework.

Comment nous avons accéléré les tests dits «rapides»


Tout a commencé par accident. Comme d'habitude. Nous avons pris en charge une fonctionnalité après l'autre, en passant des tests en même temps. Leur nombre a augmenté et le temps nécessaire pour le terminer aussi. Une fois que les tests ont commencé à ramper hors des délais qui leur étaient impartis, le processus de leur exécution a donc été interrompu de force. Les tests incomplets sont lourds d'un problème manqué dans le code.

Nous avons analysé la vitesse des tests et la tâche de les accélérer fortement est devenue pertinente. C'est ainsi qu'a commencé une étude intitulée "Les tests fonctionnent lentement - corrigez-le".

Voici trois des gros problèmes que nous avons trouvés dans les tests.

Problème 1: jsQuery mal utilisé



Toutes les données dont nous disposons sont stockées dans la base de données PostgreSQL. Surtout sous la forme de json, nous utilisons donc activement jsQuery.

Voici un exemple de requête que nous avons effectuée dans la base de données pour obtenir les données nécessaires:

SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0' AND json_data @@ 'address_name = *' AND json_data @@ 'contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1 

Il est facile de remarquer que l'exemple utilise json_data plusieurs fois de suite, bien qu'il soit correct d'écrire ceci:

 SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1 

Ces lacunes n'étaient pas trop visibles, car dans les tests, nous n'écrivons pas toutes les requêtes de nos mains, mais nous utilisons plutôt des QueryBuilders, qu'ils composent eux-mêmes après avoir spécifié les fonctions nécessaires. Nous ne pensions pas que cela pourrait affecter la vitesse d'exécution des requêtes. En conséquence, dans le code, cela ressemble à ceci:

 $qb = $this>createQueryBulder() ->selectAllBranchFields() ->fromBranchPartition() ->hasRubric() ->hasAddressName() ->hasWebsite() ->orderByRandom() ->setMaxResults(1); 

Ne répétez pas nos erreurs : s'il y a plusieurs conditions dans un champ JSONB, décrivez-les toutes dans le cadre de l'opérateur unique '@@'. Après avoir refait, nous avons accéléré le temps d'exécution de chaque demande deux fois. Auparavant, la demande décrite prenait 7500 ms, mais maintenant elle prend 3500 ms.

Problème 2: données de test supplémentaires



L'accès à notre API est assuré par clé, chaque utilisateur possède sa propre API. Auparavant, dans les tests, il était souvent nécessaire de modifier les paramètres clés. Pour cette raison, les tests sont tombés.

Nous avons décidé de créer plusieurs clés avec les paramètres nécessaires pour chaque cycle de régression afin d'éviter les problèmes d'intersection. Et puisque la création d'une nouvelle clé n'affecte pas la fonctionnalité de l'application entière, cette approche dans les tests n'affectera rien. Ils ont vécu dans de telles conditions pendant environ un an, jusqu'à ce qu'ils commencent à gérer la productivité.

Il n'y a pas beaucoup de clés - 1000 pièces. Pour accélérer l'application, nous les stockons en mémoire et les mettons à jour toutes les quelques minutes ou à la demande. Ainsi, après avoir enregistré la clé suivante, les tests ont commencé le processus de synchronisation, dont nous n'avons pas attendu la fin - nous avons reçu la réponse «504», qui était écrite dans les journaux. Dans le même temps, l'application n'a signalé aucun problème et nous avons pensé que tout fonctionnait très bien pour nous. Le processus de test de régression lui-même s'est poursuivi. Et à la fin, il s'est avéré que nous avons toujours été chanceux et nos clés ont été conservées.

Nous avons vécu dans l'ignorance jusqu'à ce que nous vérifions les journaux. Il s'est avéré que nous avons créé les clés, mais ne les avons pas supprimées après l'exécution des tests. Ainsi, nous en avons accumulé 500 000.

Ne répétez pas nos erreurs: si vous modifiez d'une manière ou d'une autre la base de données dans les tests, assurez-vous que la base de données est ramenée à son état d'origine. Après avoir nettoyé la base de données, le processus de mise à jour des clés s'est accéléré 500 fois.

Problème 3: échantillonnage aléatoire



Nous aimons tester l'application sur différentes données. Nous avons beaucoup de données et des problèmes sont régulièrement constatés. Par exemple, il y a eu un cas où nous n'avons pas téléchargé de données sur la publicité, mais les tests ont détecté ce problème à temps. C'est pourquoi dans chaque demande de nos tests, vous pouvez voir ORDER BY RANDOM ()

Lorsque nous avons examiné les résultats des requêtes, avec et sans caractère aléatoire, avec EXPLAIN, nous avons constaté une augmentation des performances de 20 fois. Si nous parlons de l'exemple ci-dessus, sans randomisation, cela fonctionne pendant 160 ms. Nous avons sérieusement réfléchi à quoi faire, car nous ne voulions pas vraiment abandonner complètement la maison aléatoire.

Par exemple, à Novossibirsk, il y a environ 150 000 entreprises, et lorsque nous avons essayé de trouver une entreprise qui a une adresse, un site Web et une rubrique, nous avons reçu un enregistrement aléatoire de presque toute la base de données. Nous avons décidé de réduire la sélection aux 100 premières entreprises qui correspondent à nos conditions. Le résultat des réflexions a été un compromis entre une sélection constante de données différentes et la vitesse:

 SELECT * FROM (SELECT * FROM firm_1 WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = "website"' LIMIT 100) random_hack ORDER BY RANDOM() LIMIT 1; 

De cette manière simple, nous n'avons rien perdu à une accélération de 20x. Le temps d'exécution d'une telle demande est de 180 ms.

Ne répétez pas nos erreurs: ce moment, bien sûr, peut difficilement être qualifié d'erreur. Si vous avez vraiment beaucoup de tests, pensez toujours à combien vous avez besoin de hasard dans les données. Le compromis entre la vitesse d'exécution des requêtes dans la base de données et l'unicité de la sélection nous a permis d'accélérer 20 fois les requêtes SQL.

Encore une fois une courte liste d'actions:


  1. Si nous spécifions plusieurs conditions pour sélectionner des données dans le champ JSONB, alors elles doivent être listées dans un seul opérateur '@@'.
  2. Si nous créons des données de test, assurez-vous de les supprimer. Même s'il semble que leur présence n'affecte pas les fonctionnalités de l'application.
  3. Si vous avez besoin de données aléatoires pour chaque exécution, nous trouvons un compromis entre l'unicité de l'échantillon et la vitesse d'exécution.

Nous avons accéléré la régression trois fois grâce à des modifications simples (et pour certains, probablement même évidentes). Maintenant, nos tests de 25K passent en 10 minutes. Et ce n'est pas la limite - nous optimisons ensuite le code. On ne sait pas combien de découvertes inattendues nous y attendent.

Source: https://habr.com/ru/post/fr419671/


All Articles