
Hallo habr
Mein Name ist Artyom Dobrovinsky, ich arbeite als Android-Entwickler bei FINCH .
Einmal, nach ein paar Pints mit einem Kollegen von einem Unternehmen, das Anzeigen für den Verkauf von MIGs und Mücken mit dem Namen Igor platziert, haben wir begonnen, statische Code-Analysatoren in CI zu diskutieren (und was noch zu besprechen ist). Es wurde die Idee geäußert, dass sie cool zu bedienen sind - aber erst, wenn Vertrauen in die logische Zuverlässigkeit des Codes besteht. Mit anderen Worten, Sie können erst über den Codestil nachdenken, nachdem alle Tests geschrieben wurden.
Ich beschloss, meinem Kollegen zuzuhören und überlegte, wie ich das Ausmaß der Katastrophe für improvisierte Anwendungen berechnen sollte. Der Blick fiel auf Sonarqube und Jacoco. Der Prozess, sie für Hallo-Welt-Projekte zu verbinden, ist elementar. Das Verbinden zu einem Android-Projekt, das in Module unterteilt ist, ist bereits schwieriger. Um Interessenten zu helfen, wurde dieser Artikel verfasst.
Habré hat bereits eine sehr gute Übersetzung des Tutorials zur Verwendung von Sonarqube - aber es ist aus dem Jahr 2016, dort ist etwas veraltet, es gibt kein Kotlin und ich finde nur die Berichtserstellung für alle BuildType-Typen überflüssig.
Ein bisschen über Bibliotheken, für diejenigen, die nicht mit ihnen vertraut sind.
Sonarqube ist eine Open-Source-Plattform für die kontinuierliche Überprüfung und Messung der Codequalität. Damit können Sie den Kampf gegen die technische Verschuldung im Laufe der Zeit verfolgen (es ist cool zu sehen, dass die technische Verschuldung gewinnt und Sie nichts dagegen unternehmen können). Sonar überwacht auch doppelten Code, potenzielle Sicherheitslücken und übermäßige Komplexität von Funktionen.
Jacoco ist eine kostenlose Bibliothek zur Berechnung der Testabdeckung eines Projekts in Java. Aber mit Kotlin werden wir ihre Freunde finden.
So verbinden Sie Sonarqube und Jacoco
Fügen Sie im build.gradle des Root-Moduls den folgenden Code hinzu:
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
- sonar.reportPath
an, wo Sonar den Bericht zur weiteren Analyse sonar.reportPath
soll.
sonar.projectBaseDir
den Ordner an, in dem die Analyse anfänglich gestartet wird. In unserem Fall ist dies $ rootDir - der Stammordner des Projekts.
sonar.coverage.exclusions
listet Ausnahmen zum Zählen der Abdeckung auf, wobei ** ein beliebiger Ordner, * ein beliebiger Dateiname oder eine beliebige Auflösung ist.
sonar.sources
ist der sonar.sources
.
sonar.tests
ist hier eine Leerzeile, damit die Tests auch von Sonarqube ausgewertet werden können.
sonar.coverage.exclusions
- Wir schließen Tests von der Analyse der Testabdeckung aus.
sonar.coverage.jacoco.xmlReportPaths
- Mit collect()
sammeln collect()
Jacoco-Berichte, um die Testabdeckung zu berechnen.
Um Jacoco zu aktivieren, ist es besser, eine jacoco.gradle
Datei zu erstellen und dort die gesamte erforderliche Logik zu schreiben. Dies wird dazu beitragen, dass das andere Build-Gradle nicht überladen wird.
Um Jacoco nicht im build.gradle jedes Teilprojekts zu registrieren, schreiben wir dessen Initialisierung im Teilprojektabschluss vor. reportsDirPath
in reportsDirPath
for submodules den Stammordner an. Von dort nimmt Sonar alle Jacoco-Berichte entgegen.
subprojects { apply plugin: 'jacoco' jacoco { toolVersion = '0.8.5' def reportsDirPath = "${project.rootDir}/app/build/reports/jacoco/${project.name}" reportsDir = file(reportsDirPath) } }
In derselben Datei schreiben wir eine Funktion zur Konfiguration von Jacoco.
Diese Funktion ist großartig, also bringe ich sie zuerst mit - und erkläre dann, was darin vor sich geht.
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) } }
Wir haben die Aufgabe getJacocoReports
, die Gruppe "Reporting". Berichte werden in den Formaten HTML und XML bereitgestellt. Alle Dateien mit Ausnahme derjenigen, die im Array "Ausgeschlossen" enthalten sind, werden analysiert. Zusätzlich zu den generierten Androyd-Dateien habe ich beschlossen, alle Fragmente und Aktivitäten, Retrofit-Schnittstellen, Pakete mit DI, benutzerdefinierte Ansichten und Bibliothekscode von der Analyse auszuschließen.
Vielleicht ändert sich diese Liste im Laufe der Zeit.
classDirectories
- classDirectories
an, wo nach Code für die Analyse classDirectories
werden soll. Wir fügen hier sowohl Java- als auch Kotlin-Dateien ein.
sourceDirectories
- sourceDirectories
, wo Jacoco nach Quelldateien sucht.
executionData
- wie im Fall von Sonar eine Angabe, wo der Bericht zur Berechnung der Abdeckung erstellt wird.
Außerdem müssen Sie in jacoco.gradle die Konfiguration für alle Module mit der obigen Funktion hinzufügen:
allprojects { project -> configureJacoco(project) project.tasks.withType(Test) { enabled = true jacoco.includeNoLocationClasses = true } }
Und eine Aufgabe zum Sammeln der generierten Berichte:
task getJacocoReports() { group = "Reporting" subprojects.forEach { subproject -> subproject.tasks.withType(JacocoReport).forEach { task -> dependsOn task } } }
Ausführen von Sonarqube über die Befehlszeile
Es ./gradlew % % && ./gradlew jacocoAggregateReports && ./gradlew sonarqube
einfach: ./gradlew % % && ./gradlew jacocoAggregateReports && ./gradlew sonarqube
. Befehle werden durch &&
, da die Ausführung unterbrochen werden sollte, wenn der vorherige Schritt nicht erfolgreich war.
Was passiert auf Befehl oben:
- Führen Sie zuerst die Tests aus (gleichzeitig generieren wir alle erforderlichen Dateien im Build-Ordner).
- Generieren Sie einen Jacoco-Bericht.
- Starten Sie Sonarqube.
Als nächstes müssen Sie zur Site gehen, das Projekt nicht ausführen und das Ausmaß der Katastrophe untersuchen. Die Projektseite zeigt das Ergebnis der letzten Prüfung.
Mit Sonarqube wird die Vorstellung vom Stand des Projekts viel vollständiger. Es ist einfacher, den technischen Schuldenbestand anzupassen, als neue Entwickler zu finden (in jedem Streitpunkt gibt Sonarqube ein Argument an, warum es nicht akzeptiert wird, so zu schreiben - das Lesen dieser Erklärungen kann sehr nützlich sein), und einfach - Wissen ist Macht .
Das ist alles, Leute!
Frage an die Leser - Womit analysieren Sie den Code und messen die Testabdeckung? Sehen Sie den Punkt überhaupt?