SonarQube

SonarQube (vroeger Sonar genoemd) is een opensource platform om code kwaliteit mee te managen. Het is primair een hulpmiddel voor ontwikkelaars om goede kwaliteit code op te leveren. Vanuit een algemeen overzicht van de codekwaliteit kan er doorgeklikt worden naar de specifieke coderegel(s) waar het probleem zich bevind. SonarQube is open source en gemakkelijk uit te breiden door middel van plugins. De applicatie ondersteund vele programmeertalen, zoals Java, JavaScript, C#, C++, COBOL, PHP etc. Deze talen kunnen geanalyseerd worden om naar kwaliteit aandachtspunten te zoeken, zoals code duplicaten, falende testen en allerlei andere problemen. In dit artikel behandel ik een aantal van de nieuwe en onbekende features.

In een editie van Java Magazine over software kwaliteit mag SonarQube eigenlijk niet ontbreken. SonarQube is praktisch de standaardtool voor iedere Java ontwikkelaar die zijn code automatisch wil controleren. SonarQube is vooral bekend geworden vanwege de integratie met Checkstyle, PMD en FindBugs. Het grappige is dat de ondersteuning van die drie tools in SonarQube inmiddels minimaal is. SonarQube heeft praktisch alle regels van Checkstyle, PMD en FindBugs vervangen door een eigen set aan regels. Die eigen set aan regels bevat ook nog eens een aantal extra regels. Doordat SonarQube niet meer afhankelijk is van deze applicaties, kan men sneller nieuwe features inbouwen. Bijvoorbeeld, wanneer een nieuwe Java versie uitkomt, dan hoeven ze niet langer te wachten tot de andere applicaties ondersteuning voor die nieuwe Java versie inbouwen. Daarnaast hoeft de code nog maar één keer gescand te worden in plaats van één scan per externe applicatie.

Naast deze regels zijn er nog veel meer handige dingen in SonarQube. Wat mij echter opviel, was dat veel ontwikkelaars nog niet alle interessante features kennen. Daarom heb ik eerder al een artikel geschreven voor Java Magazine waarin ik bijvoorbeeld codereviews in SonarQube behandel. In de rest van dit artikel zal ik een aantal andere SonarQube features behandelen die nog niet zo bekend zijn en niet in het vorige artikel behandelt zijn.

 

Pitest

Met behulp van mutation testing kun je op een snelle manier de kwaliteit van je testen controleren. Unit testen zonder assert’s zul je er bijvoorbeeld zo uit kunnen halen. Ook unit testen die de verschillende scenario’s en randgevallen niet goed testen, vind je gemakkelijk met mutation testing. Wil je meer weten over mutation testing dan kan ik het artikel dat Roy van Rijn daarover heeft geschreven aanraden.

Pitest is een vorm van mutation testing. In het kort werkt het als volgt:

  • Een test wordt uitgevoerd en slaagt;
  • De onderliggende code (dus niet de test zelf) wordt aangepast;
    – Relationele operatoren worden aangepast;
     Return waarden worden aangepast;
    – Boolean waarden worden aangepast;
  • Vervolgens wordt de test een aantal keer uitgevoerd om de verschillende code wijzigingen te testen.

Als de test nog steeds slaagt, dan is het blijkbaar geen hele goede test. De code kan namelijk aangepast worden zonder dat de test omvalt.

In listing 1 is een stuk voorbeeldcode te zien. In listing 2 staan twee testen die de code uit listing 1 testen. De eerste test slaagt eigenlijk altijd aangezien de code één van de twee inputwaarden teruggeeft en beide inputwaarden 20 en 100 zijn lager dan 101. Dus ook als we het ‘if’ statement weg zouden halen en altijd de ‘boundary’ waarde terug zouden geven, dan slaagt de test nog steeds.

De tweede test bevat niet eens een assert en zal vrijwel altijd slagen. Dit zijn twee voorbeelden van testen die niet zo goed zijn. Dat komt misschien, omdat ze expres zo geschreven zijn om voldoende coverage te halen. Het kan echter ook zo zijn dat de code aangepast is en de testen vervolgens niet goed aangepast zijn. Kortom, er zijn veel wegen die tot een slechte test leiden en het is zaak om die slechte testen zo snel mogelijk op te sporen.


public int getValueOrBoundary(int value, int boundary) {
if (value < boundary) {
return value;
}
return boundary;
}

Listing 1: Pitest code voorbeeld



@Test
public void testGetValueOrBoundary() {
    assertTrue(pi.getValueOrBoundary(20, 100) < 101);
}

@Test
public void testGetValueOrBoundaryNoAssert() {
    pi.getValueOrBoundary(40, 200);
}

Listing 2: Pitest test voorbeeld

Het is vrij eenvoudig om ondersteuning voor Pitest aan je project toe te voegen. Allereerst voeg je de Pitest plugin toe aan SonarQube. Vervolgens voeg je de Maven Pitest plugin toe in je Maven POM configuratie onder de build/plugins sectie. Hierbij moet je opgeven welke classes je wilt testen. Op die manier kun je sommige testen uitsluiten van Pitest.


<plugin>
  <groupId>org.pitest</groupId>
  <artifactId>pitest-maven</artifactId>
  <version>LATEST</version>
  <configuration>
    <inScopeClasses>
      <param>com.example*</param>
    </inScopeClasses>
    <targetClasses>
      <param>com.example*</param>
    </targetClasses>
    <outputFormats>
      <outputFormat>XML</outputFormat> 
    </outputFormats>
  </configuration>
</plugin>

Listing 3

Daarna voer je twee commando’s uit. Het eerste commando voert Pitest uit en het tweede commando leest de Pitest resultaten in SonarQube in.


mvn org.pitest:pitest-maven:mutationCoverage
mvn sonar:sonar -Dsonar.pitest.mode=reuseReport

Listing 4

Vervolgens kun je binnen SonarQube overzichtelijk zien welke testen van onvoldoende kwaliteit zijn. In figuur 1 is te zien welke wijzigingen (mutations) er in de code gemaakt kunnen worden zonder dat de test faalt. Het is dus zaak om deze gevallen goed te testen. SonarQube toont dus niet hoeveel ‘goede’ coverage je hebt, het toont slechts welke code je beter dient te testen.

Figuur 1 SonarQube Pitest

Coverage van een draaiende JVM meten

Tegenwoordig is het vrij normaal om unit test coverage te meten en soms zelfs een bepaald minimum aan coverage af te dwingen. Ook bij integratie testen wordt er steeds meer naar de coverage gekeken. Maar hoe zit het met de coverage van functionele testen met bijvoorbeeld Fitnesse of Selenium? Of met de coverage van acceptatie testen dan wel performance testen? Vaak kijken we daar nog niet naar, terwijl het wel erg interessant kan zijn.

Stel je bijvoorbeeld eens voor dat je functionele testen hebt met bijna 100 procent dekking van de verschillende features. Wat als blijkt dat de code coverage van die functionele testen slechts 50 procent is? Wellicht dat er dan een aantal uitzonderingsgevallen niet getest worden, maar het kan ook zo zijn dat er dode code in je applicatie is. Door de coverage te meten kun je zien hoeveel en welke code getest wordt.

Het meten van de coverage van een draaiende JVM kan door een Jacoco agent aan de applicatieserver te koppelen. Daarvoor dien je eerst Jacoco te downloaden inclusief agent. Vervolgens kun je bijvoorbeeld onderstaande configuratie toevoegen aan de catalina.bat van Tomcat. Daarbij geef je de volgende zaken op:

  • javaagent: directory waar je de agent hebt gedownload;
  • destfile: file waarin de coverage resultaten worden opgeslagen;
  • append: false als er iedere keer bij het starten van de JVM een nieuwe coverage file aangemaakt moet worden. True als je steeds dezelfde coverage file wilt gebruiken om te ‘appenden’;
  • Includes: om aan te geven welke Java packages gemeten moeten worden. Let op dat je hier alleen de packages van je eigen applicatie opgeeft. Anders zullen ook de standaard Java packages meegenomen worden wat nadelig voor de performance is.

set JACOCO=-javaagent:[agent_directory]\jacocoagent.jar=destfile=[resultfile_dir]\jacoco.exec,append=false,includes=com.dockerpi.*
set JAVA_OPTS=%JAVA_OPTS% %JACOCO%

Listing 5

Na deze aanpassingen kun je de applicatie server starten. Vervolgens kun je de testen uitvoeren. Als de testen zijn uitgevoerd, dan moet je meestal de applicatie server even stoppen voordat de coverage resultaten weggeschreven worden in het bestand. Nadat de coverage resultaten in het bestand zijn weggeschreven, kun je deze inlezen in SonarQube. Bijvoorbeeld met de commando’s hieronder, deze zorgen ervoor dat zowel de unit test resultaten als de integratie test resultaten ingelezen worden in SonarQube. SonarQube heeft het over integratietesten, maar het kunnen bijvoorbeeld ook functionele testen zijn.


mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install
mvn sonar:sonar -Dsonar.jacoco.itReportPath=[resultfile_dir]\jacoco.exec

Listing 6

Vervolgens kun je in SonarQube op hoog niveau de unit test, integratie test en overall coverage zien. Bijvoorbeeld door middel van widgets zoals in Figuur 2. Waarbij overall coverage een samenvoeging van de unit test coverage en integratie test coverage is. Met de overall coverage kun je bijvoorbeeld kijken of alle code in ieder geval door één type test is gecontroleerd. Vanuit dit overzicht kan er doorgeklikt worden om op code niveau te kijken welke regels en branches getest worden door de verschillende type testen. Zo is in figuur 3 te zien dat die specifieke regel niet door een unit test, maar wel door een integratietest gecontroleerd wordt.

Figuur 2 Overzicht van unit en integratietest coverage

Figuur 3 Codevoorbeeld waarbij links te zien is dat er geen unit test coverage is (rood), maar wel integratietest coverage (groen)

Met behulp van de Jacoco agent is het niet alleen mogelijk om de coverage van testen te meten. Je zou de agent ook op een productie omgeving kunnen gebruiken om te kijken welke stukken code door klanten gebruikt worden. Houd er in dat geval wel rekening mee dat het gebruik van de agent invloed heeft op de performance.

Cross project duplication

Met SonarQube is het mogelijk om binnen een project te zoeken naar code duplicaten. Die code kun je vervolgens aanpassen, zodat het nog maar één keer voorkomt. Dat komt de onderhoudbaarheid weer ten goede. Echter wordt er vaak niet alleen binnen een project code gekopieerd. Het is vaak handig om even een stukje code van een ander project te gebruiken. Dat kan netjes door die code los te trekken en er een aparte library van te maken. Vaak gebeurd dat niet op die manier, waardoor dezelfde code zich in meer projecten bevind en ook onderhouden moet worden.

Met SonarQube is het mogelijk om deze code duplicaten over projecten heen op te sporen (Figuur 4). Dit moet je wel expliciet activeren (Figuur 5), want het heeft impact op de performance. Tenslotte moet hiervoor bij iedere SonarQube analyse van een project de code met alle andere projecten vergeleken worden.

Figuur 4 De code in SomeViolations.java van de DockerPiExample applicatie is een kopie van de code in SomeViolations.java van de TestApplication application

Figuur 5 Activeren van cross project duplication detectie

SonarLint

Het is natuurlijk handig om SonarQube uit te voeren op de buildserver. Alleen, ben je dan niet al te laat? Tenslotte heb je de code al in versiebeheer gezet en kan de hele wereld zien welke fouten je gemaakt hebt. Natuurlijk zou je zelf een SonarQube instantie op je machine kunnen draaien en de code kunnen controleren voor je het in versiebeheer plaatst. Dat is echter nogal wat werk, met name ook omdat je de instellingen tussen de centrale SonarQube server en jouw instantie synchroon zult moeten houden.

Gelukkig kwam SonarQube een aantal jaren geleden met plugins voor bijvoorbeeld Eclipse en IntelliJ. Die werkten al een stuk beter, maar je moest nog steeds de SonarQube analyse van je code zelf starten. Inmiddels is er SonarLint (Figuur 6) waarmee je on the fly in je ontwikkelomgeving feedback krijgt over de code die je schrijft. Op dit moment werkt SonarLint alleen met een default SonarQube profile, maar de verwachting is dat op korte termijn ook eigen profiles ondersteund worden.

 Figuur 6 SonarLint controleert de code in je ontwikkelomgeving continue op fouten

 

Conclusie

De ontwikkeling van SonarQube gaat nog steeds hard en er komen steeds meer interessante features bij. De features die ik in dit artikel behandel, kunnen jou en je project helpen om de kwaliteit te bepalen en indien nodig te verbeteren. Wil je zelf op de hoogte blijven van de ontwikkelingen van SonarQube dan kan ik de blog en andere informatiebronnen hieronder aanraden.

 

Referenties

Leave a Reply

Your email address will not be published. Required fields are marked *