Mesurer la qualité du code d'application Android avec Sonarqube et Jacoco en 2019


Bonjour, Habr!


Je m'appelle Artyom Dobrovinsky, je travaille en tant que développeur Android chez FINCH .


Une fois, après quelques pintes avec un collègue d'une entreprise qui place des annonces pour vendre des MIG et des moustiques nommés Igor, nous avons commencé à discuter des analyseurs de code statique dans CI (et de quoi d'autre à discuter). L'idée a été exprimée qu'ils étaient cool à utiliser - mais seulement après avoir eu confiance dans la fiabilité logique du code. En d'autres termes, vous ne pouvez penser au style de code qu'après avoir écrit tous les tests.


J'ai décidé d'écouter mon collègue et j'ai réfléchi à la façon de calculer l'ampleur de la catastrophe pour des applications improvisées. La vue est tombée sur Sonarqube et Jacoco. Le processus de connexion entre eux pour les projets du monde bonjour est élémentaire. Les connecter à un projet Android, divisé en modules, est déjà plus difficile. Afin d'aider les personnes intéressées, cet article a été écrit.


Habré a déjà une très bonne traduction du tutoriel sur l'utilisation de Sonarqube - mais c'est à partir de 2016, quelque chose est dépassé là-bas, il n'y a pas de kotlin et je trouve juste la génération de rapports pour tous les buildType redondants.


Un peu sur les bibliothèques, pour ceux qui ne les connaissent pas.


Sonarqube est une plate-forme open source pour l'inspection continue et la mesure de la qualité du code. Il vous permet de suivre la lutte contre la dette technique au fil du temps (c'est cool de voir que la dette technique gagne, et vous ne pouvez rien y faire). Sonar surveille également le code en double, les vulnérabilités potentielles et la complexité excessive des fonctions.


Jacoco est une bibliothèque gratuite pour calculer la couverture de test d'un projet en Java. Mais avec Kotlin, nous nous ferons des amis.


Comment connecter Sonarqube et Jacoco


Dans le build.gradle du module racine, ajoutez le code suivant:


apply plugin: 'android.application' apply plugin: 'org.sonarqube' sonarqube { properties { property "sonar.host.url", "%url   sonarqube%" property "sonar.login", "%%" property "sonar.projectName", "% %" property "sonar.projectKey", "%  %" property "sonar.reportPath", "${project.buildDir}/sonarqube/test.exec" property "sonar.projectBaseDir", "$rootDir" property "sonar.sources", "." property "sonar.tests", "" property "sonar.coverage.exclusions", "**/src/androidTest/**, **/src/test/**" property "sonar.coverage.jacoco.xmlReportPaths", fileTree(include: ['*/*/jacoco*.xml'], dir: "$rootDir/app/build/reports/jacoco").collect() } } 

sonar.reportPath - indiquez où Sonar doit placer le rapport pour une analyse plus approfondie.
sonar.projectBaseDir spécifie le dossier dans lequel l'analyse sera lancée initialement; dans notre cas, il s'agit de $ rootDir - le dossier racine du projet.
sonar.coverage.exclusions énumérant les exceptions pour le comptage de la couverture, où ** est n'importe quel dossier, a * est n'importe quel nom de fichier ou résolution.
sonar.sources est le dossier source.
sonar.tests est une ligne vide ici afin que les tests puissent également être analysés par Sonarqube.
sonar.coverage.exclusions - nous excluons les tests de l'analyse de la couverture des tests.
sonar.coverage.jacoco.xmlReportPaths - en utilisant collect() collectons des rapports Jacoco pour calculer la couverture du test.


Pour activer Jacoco, il est préférable de créer un fichier jacoco.gradle et d'y écrire toute la logique nécessaire. Cela aidera à éviter d'encombrer l'autre build.gradle.


Afin de ne pas enregistrer Jacoco dans le build.gradle de chaque sous-projet, nous prescrivons son initialisation dans la clôture des sous-projets. Dans reportsDirPath pour les sous-modules, spécifiez le dossier racine. À partir de là, Sonar prendra tous les rapports Jacoco.


 subprojects { apply plugin: 'jacoco' jacoco { toolVersion = '0.8.5' def reportsDirPath = "${project.rootDir}/app/build/reports/jacoco/${project.name}" reportsDir = file(reportsDirPath) } } 

Dans le même fichier, nous écrivons une fonction pour configurer Jacoco.
Cette fonction est géniale, donc je vais d'abord l'apporter - et ensuite je vais expliquer ce qui s'y passe.


 def configureJacoco = { project -> def variantName = project.name project.tasks.create(name: "getJacocoReports", type: JacocoReport) { group = "Reporting" description = "Generate Jacoco coverage reports for the $variantName build." reports { html.enabled = true xml.enabled = true } def excludes = [ '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/AndroidManifest.xml', '**/*Test*.*', 'android/**/*.*', 'androidx/**/*.*', '**/*Fragment.*', '**/*Activity.*', '**/*Api.*', '**/injection/**/*.class', '**/ui/**/*.class', %  build- % ] def javaClasses = fileTree(dir: "${project.buildDir}/intermediates/javac", excludes: excludes) def kotlinClasses = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes", excludes: excludes) classDirectories = files([javaClasses, kotlinClasses]) sourceDirectories = files([ "${project.projectDir}/src/main/java", "${project.projectDir}/src/main/kotlin", ]) executionData = files(fileTree(include: ['*.exec'], dir: "${project.buildDir}/jacoco").files) } } 

Nous avons créé la tâche getJacocoReports , le groupe «Reporting». Les rapports seront fournis aux formats html et xml. Tous les fichiers à l'exception de ceux inclus dans le tableau exclut seront analysés. En plus des fichiers Androyd générés, j'ai décidé d'exclure de l'analyse tous les fragments et activités, interfaces Retrofit, package avec DI, vues personnalisées et code de bibliothèque.
Peut-être que cette liste changera avec le temps.


classDirectories - une indication de l'endroit où chercher le code pour l'analyse. Nous incluons ici les fichiers java et kotlin.
sourceDirectories - spécifiez où Jacoco recherche les fichiers source.
executionData - comme dans le cas du Sonar, une indication de l'endroit où le rapport sera généré pour calculer la couverture.


Toujours dans jacoco.gradle, vous devez ajouter sa configuration pour tous les modules en utilisant la fonction ci-dessus:


 allprojects { project -> configureJacoco(project) project.tasks.withType(Test) { enabled = true jacoco.includeNoLocationClasses = true } } 

Et une tâche de collecte des rapports générés:


 task getJacocoReports() { group = "Reporting" subprojects.forEach { subproject -> subproject.tasks.withType(JacocoReport).forEach { task -> dependsOn task } } } 

Exécution de Sonarqube via la ligne de commande


Cela ./gradlew % % && ./gradlew jacocoAggregateReports && ./gradlew sonarqube simplement: ./gradlew % % && ./gradlew jacocoAggregateReports && ./gradlew sonarqube . Les commandes sont exécutées via && , car l'exécution doit être interrompue si l'étape précédente n'a pas réussi.


Que se passe-t-il sur commande ci-dessus:


  1. Tout d'abord, exécutez les tests (en même temps, nous générons tous les fichiers nécessaires dans le dossier de construction).
  2. Générez un rapport Jacoco.
  3. Lancez Sonarqube.

Ensuite, vous devez vous rendre sur le site, échouer dans le projet et regarder l'ampleur de la catastrophe. La page du projet affiche le résultat de la dernière vérification.


Avec Sonarqube, l'idée de l'état du projet devient de plus en plus complète. Il est plus facile d'ajuster l'arriéré technique de la dette que de s'attaquer aux développeurs novices (dans chaque chipie, Sonarqube explique pourquoi il n'est pas accepté d'écrire comme ça - la lecture de ces explications peut être très utile), et simplement - la connaissance est le pouvoir .


C'est tout, les amis!


Question aux lecteurs - qu'utilisez-vous pour analyser le code et mesurer la couverture des tests? Voyez-vous le point du tout?

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


All Articles