Testez ceci: comment nous déterminons les tests à exécuter lors des vérifications de demandes de tirage

Bonjour, Habr! Je m'appelle Egor Danilenko. Je développe une plate-forme numérique pour les services bancaires par Internet aux entreprises de Sberbank Business Online, et aujourd'hui, je veux vous parler de la procédure de développement de CI que nous avons adoptée.

Comment les modifications apportées aux développeurs sont-elles introduites dans la branche de publication? Le développeur apporte des modifications localement et pousse dans notre système de contrôle de version. Nous utilisons Bitbucket avec le plugin d'un auteur (nous avons écrit à propos de ce plugin plus tôt ici ). Sur ces changements, l'assemblage est lancé et les tests sont poursuivis (unit, intégration, fonctionnel). Si l'assemblage n'a pas échoué et que tous les tests ont réussi, ainsi qu'après un examen réussi, la demande d'extraction est versée dans la branche principale.

Mais au fil du temps, le nombre d'équipes a augmenté. Le nombre de tests a augmenté proportionnellement. Nous avons compris qu'un tel nombre d'équipes accélérerait l'apparition du problème de la «lente traction-demande-vérification» et qu'il deviendrait impossible de développer un produit. Actuellement, nous avons environ 40 équipes. Avec de nouvelles fonctionnalités, ils apportent de nouveaux tests, qui doivent également être exécutés sur les demandes d'extraction.

Nous pensions que ce serait cool si nous savions quels tests exécuter pour changer un morceau de code particulier.

Et c'est ainsi que nous avons résolu ce problème.

Énoncé du problème


Il existe un projet avec des tests, et nous voulons déterminer quels tests doivent être exécutés lorsqu'un certain fichier est «touché».

Nous connaissons tous la bibliothèque de couverture de code EclEmma JaCoCo. Nous l'avons pris comme base.

Un peu sur JaCoCo


JaCoCo est une bibliothèque pour mesurer la couverture de code avec des tests. Le travail est basé sur l'analyse d'octets de code. L'agent collecte les informations d'exécution et les télécharge à la demande ou à l'arrêt de la machine virtuelle Java.

Il existe trois modes de collecte de données:

  1. Système de fichiers: après l'arrêt de la JVM, les données seront écrites dans un fichier.
  2. TCP Socket Server: vous pouvez connecter des outils externes à la JVM et recevoir des données via le socket.
  3. TCP Socket Client: Lorsqu'il est lancé, l'agent JaCoCo se connecte à un point de terminaison TCP spécifique.

Nous avons choisi la deuxième option.

Solution


Il est nécessaire de réaliser la capacité d'exécuter des applications et les tests eux-mêmes avec l'agent JaCoCo.

Tout d'abord, nous ajoutons à gradle la possibilité d'exécuter des tests avec l'agent JaCoCo.

L'agent Java peut être démarré:

-javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2] 

Ajoutez une dépendance à notre projet:

 dependencies { compile 'org.jacoco:org.jacoco.agent:0.8.0' } 

Nous devons seulement commencer par l'agent pour collecter des statistiques, nous ajoutons donc l'indicateur withJacoco avec la valeur par défaut false à gradle.properties. Nous spécifions également le répertoire où seront collectées les statistiques, l'adresse et le port.

Ajoutez l'argument jvm avec l'agent à la tâche de lancement du test:

 if (withJacoco.toBoolean()) { … jvmArgs "-javaagent:${tempPath}=${jacocoArgs.join(',')}".toString() } 

Maintenant, après chaque réussite du test, nous devons collecter des statistiques avec JaCoCo. Pour ce faire, écrivez l'écouteur TestNG.

 public class JacocoCoverageTestNGListener implements ITestListener { private static final IntegrationTestsCoverageReporter reporter = new IntegrationTestsCoverageReporter(); private static final String TEST_NAME_PATTERN = "%s.%s"; @Override public void onTestStart(ITestResult result) { reporter.resetCoverageDumpers(String.format(TEST_NAME_PATTERN, result.getInstanceName(), result.getMethod().getMethodName())); } @Override public void onTestSuccess(ITestResult result) { reporter.report(String.format(TEST_NAME_PATTERN, result.getInstanceName(), result.getMethod().getMethodName())); } } 

Ajoutez un écouteur à testng.xml et commentez-le, car nous n'en avons pas besoin lors d'un test normal.

Nous avons maintenant la possibilité d'exécuter des tests avec l'agent JaCoCo, avec chaque statistique de test réussie sera collectée.

Un peu plus sur la façon dont reporter est implémenté pour collecter des statistiques.
Pendant l'initialisation du reporter, une connexion est établie avec les agents, un répertoire est créé où les statistiques seront stockées et les statistiques seront collectées.

Ajoutez la méthode de rapport:

 public void report(String test) { reportClassFiles(test); reportResources(test); } 

La méthode reportClassFile crée le dossier jvm dans le répertoire des statistiques, dans lequel les statistiques collectées par les fichiers de classe sont stockées.

La méthode reportResources crée le dossier des ressources, qui stocke les statistiques collectées sur les ressources (pour tous les fichiers non-classe).

Le rapport contient toute la logique de connexion à un agent, de lecture de données à partir d'un socket et d'écriture dans un fichier. Implémenté par des outils fournis par JaCoCo, tels que org.jacoco.core.runtime.RemoteControlReader / RemoteControlWriter.

Les fonctions reportClassFiles et reportResources utilisent la fonction générique dumpToFile.

 public void dumpToFile(File file) { try (Writer fileWriter = new BufferedWriter(new FileWriter(file))) { for (RemoteControlReader remoteControlReader : remoteControlReaders) { remoteControleReader.setExecutionDataVisitor(new IExecutionDataVisitor() { @Override public void visitClassExecution(ExecutionData data) { if (data.hasHits()) { String name = data.getName(); try { fileWriter.write(name); fileWriter.write('\n'); } catch (IOException e) { throw new RuntimeException(e); } } } }); } } } 

Le résultat de la fonction sera un fichier avec un ensemble de classes / ressources affectées par ce test.

Et donc, après avoir exécuté tous les tests, nous avons un répertoire avec des statistiques sur les fichiers de classe et les ressources.

Il reste à écrire un pipeline pour la collecte de statistiques quotidiennes et à ajouter au lancement du pipeline des vérifications des demandes d'extraction.

Nous ne sommes pas intéressés par les étapes de l'assemblage du projet, mais nous examinerons plus en détail l'étape de publication des statistiques.

 stage('Agregate and parse result') { def inverterInJenkins = downloadMavenDependency( url: NEXUS_RELEASE_REPOSITORY, group: '', name: 'coverage-inverter', version: '0', type: 'jar', mavenHome: wsp ) dir('coverage-mapping') { gitFullCheckoutRef '', '', 'coverage-mapping', "refs/heads/${params.targetBranch}-integration-tests" sh 'rm -rf *' } sh "ls -lRa ..//out/coverage/" def inverter = wsp + inverterInJenkins.substring(wsp.length()) sh "java -jar ${inverter} " + "-d ..//out/coverage/jvm " + "-o coverage-mapping//jvm " + "-i coverage-config/jvm-include " + "-e coverage-config/jvm-exclude" sh "java -jar ${inverter} " + "-d ..//out/coverage/resources " + "-o coverage-mapping//resources " + "-i coverage-config/resources-include " + "-e coverage-config/resources-exclude" gitPush '', '', 'coverage-mapping', "${params.targetBranch}-integration-tests" } 

Dans la cartographie de couverture, nous devons stocker le nom du fichier et à l'intérieur une liste de tests qui doivent être exécutés. Étant donné que le résultat de la collecte de statistiques est le nom du test qui stocke l'ensemble des classes et des ressources, nous devons inverser le tout et exclure les données supplémentaires (classes des bibliothèques tierces).

Nous inversons nos statistiques et poussons vers notre référentiel.

Des statistiques sont collectées tous les soirs. Il est stocké dans un référentiel distinct pour chaque branche de version.

Bingo!

Maintenant, lors de l'exécution des tests, il nous suffit de trouver le fichier modifié et de déterminer les tests à exécuter.

Les problèmes rencontrés:

  • Étant donné que JaCoCo ne fonctionne qu'avec le bytecode, il est impossible de collecter des statistiques sur des fichiers tels que .xml, .gradle, .sql à partir de la boîte. Par conséquent, nous avons dû «accélérer» nos décisions.
  • Contrôle constant de la pertinence des statistiques et de la fréquence de l'assemblage, si l'assemblage nocturne échouait pour une raison quelconque, les statistiques «d'hier» seront utilisées pour vérification dans les demandes de tirage.

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


All Articles