Sonar requires the coverage files to be provided during the project scanning. Those data files are generally recorded by plugins, servers, interpreters, ... when executing the tests and made accessible to the sonar scanner.
This depends of the language and packager (like maven/gradle, ..) used in the project to execute the tests and build the artifacts.
Sonar recognizes 2 types of testing that can be executed in a build for the coverage report, and it computes the union of the unit and integration test results.
- Unit tests
It tests the classes or methods directly, generally by using specific test framework (Junit, testNG, etc ...)
The "container framework" used behind the scene (Spring/ JBoss/ EJB or whatever) SHOULD NOT be used in the Unit test scope.
External calls to test servers or third party components/anything MUST NOT be done here. These type of tests should be executable by anybody locally in an IDE.
Due to that, it's sometimes required to make use of mock frameworks to replace the code that can't be triggered because it does require unreachable components
The mocking SHOULD NEVER consume the developer's bandwidth, that means mocking the code should be straight-forward.
Don't waste your time into a complicated implementation, for that there is a second test phase called "integration tests"
Also DO NOT add "isTest" flag in your code to replace the mock, this is a common test anti pattern that lead the useless tests. - Integration tests
It tests the code within the "container framework" and can make use of 3rd party components, like databases.
The 3rd party components could be emulated, or even better, could be physically mounted locally, with the help of docker for instance.
The starting of the those 3rd party components can be initiated/shut-downed by the build itself in a "Pre-integration/Post-integration" phase.
Here are some tips to setup that coverage per packager and per language
For Maven
When using MAVEN, at least one pom.xml must be defined, the project contains normally a central pom.xml that is used to build the entire project (it can contain multiple maven sub-modules)
In this pom.xml, the sonar plugin must be defined
<plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>x.x</version> </plugin>
There is also a need to setup some properties for SONAR in order to indicate how the scans should be done, this include the coverage data files that must be used
<properties> .... <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin> <!-- Default Sonar configuration --> <sonar.jacoco.reportPath>target/code-coverage/jacoco-ut.exec</sonar.jacoco.reportPath> <sonar.jacoco.itReportPath>target/code-coverage/jacoco-it.exec</sonar.jacoco.itReportPath> .... </properties>
Note that if you import the org.onap.oparent.oparent those settings are already included, so there is no need to add them unless you want to override those settings
Java
For java there are different plugins/properties that must be included in the pom.xml to execute the tests and record the coverage during the execution
Maven Properties
<properties> ... <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> <sonar.surefire.reportsPath>${project.build.directory}/surefire-reports</sonar.surefire.reportsPath> <sonar.jacoco.reportPath>${project.build.directory}/coverage-reports/jacoco.exec</sonar.jacoco.reportPath> <sonar.jacoco.itReportPath>${project.build.directory}/coverage-reports/jacoco-it.exec</sonar.jacoco.itReportPath> <sonar.jacoco.reportMissing.force.zero>true</sonar.jacoco.reportMissing.force.zero> <sonar.projectVersion>${project.version}</sonar.projectVersion> <!-- Enable language to disable other language analysis --> <sonar.language>java</sonar.language> <sonar.exclusions>src/main/resources/**</sonar.exclusions> ... </properties>
Surefire (Unit tests)
This one is used to execute the unit tests, various config are available depending of the project need (https://maven.apache.org/surefire/maven-surefire-plugin/index.html)
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>X.XX.X</version> <configuration> <forkCount>1</forkCount> <reuseForks>true</reuseForks> <useSystemClassLoader>false</useSystemClassLoader> </configuration> </plugin>
Failsafe (Integration tests)
This one is used to execute the integration tests, various config are available depending of the project need (https://maven.apache.org/surefire/maven-failsafe-plugin/index.html)
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>X.XX.X</version> <executions> <execution> <id>integration-tests</id> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <additionalClasspathElements> <additionalClasspathElement>${project.build.directory}/classes</additionalClasspathElement> </additionalClasspathElements> <includes> <include>**/*ItCase.java</include> </includes> <forkCount>1</forkCount> <reuseForks>false</reuseForks> <useSystemClassLoader>false</useSystemClassLoader> </configuration> </execution> </executions> </plugin>
Jacoco
This one is used to record the coverage of the unit tests and integration tests, also a result merge for the developer IDE (https://www.eclemma.org/jacoco/trunk/doc/maven.html)
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.2</version> <configuration> <dumpOnExit>true</dumpOnExit> <includes> <include>org.onap.*</include> </includes> <propertyName>surefireArgLine</propertyName> </configuration> <executions> <execution> <id>pre-unit-test</id> <goals> <goal>prepare-agent</goal> </goals> <configuration> <destFile>${project.build.directory}/coverage-reports/jacoco.exec</destFile> <!-- <append>true</append> --> </configuration> </execution> <execution> <id>pre-integration-test</id> <phase>pre-integration-test</phase> <goals> <goal>prepare-agent</goal> </goals> <configuration> <destFile>${project.build.directory}/coverage-reports/jacoco-it.exec</destFile> <!-- <append>true</append> --> </configuration> </execution> <execution> <goals> <goal>merge</goal> </goals> <phase>post-integration-test</phase> <configuration> <fileSets> <fileSet implementation="org.apache.maven.shared.model.fileset.FileSet"> <directory>${project.build.directory}/coverage-reports</directory> <includes> <include>*.exec</include> </includes> </fileSet> </fileSets> <destFile>${project.build.directory}/jacoco-dev.exec</destFile> </configuration> </execution> </executions> </plugin>
Note that for the integration tests, it could more complex than the following Jacoco plugin usage.
If it's a remote server or anything else the jacoco agent must be provided to the running JVM to do the coverage recording (https://www.eclemma.org/jacoco/trunk/doc/agent.html), then brought back to build folder.
Docker Plugin (Integration tests)
This plugin can be used to start containers before doing the integration tests. The following example starts Mariadb and Python before the integration tests phase.
<plugin> <groupId>io.fabric8</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.26.0</version> <configuration> <verbose>true</verbose> <apiVersion>1.23</apiVersion> <images> <image> <name>library/mariadb:10.1.11</name> <alias>mariadb</alias> <run> <env> <MYSQL_ROOT_PASSWORD>secret</MYSQL_ROOT_PASSWORD> </env> <hostname>mariadb</hostname> <volumes> <bind> <volume>${project.basedir}/extra/sql/:/docker-entrypoint-initdb.d</volume> <volume>${project.basedir}/extra/docker/mariadb/conf1:/etc/mysql/conf.d</volume> </bind> </volumes> <wait> <log>socket: '/var/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution</log> <time>600000</time> </wait> <ports> <port>${docker.mariadb.port.host}:3306</port> </ports> </run> </image> <image> <name>library/python:2-slim</name> <alias>python</alias> <run> <hostname>python</hostname> <volumes> <bind> <volume>${project.basedir}/src/test/resources/http-cache/:/usr/src/http-cache-app</volume> <volume>${project.basedir}/src/test/resources/http-cache/example/:/usr/src/http-cache-app/data-cache</volume> </bind> </volumes> <wait> <tcp> <ports> <port>8080</port> </ports> <mode>direct</mode> </tcp> <time>120000</time> </wait> <ports> <port>${docker.http-cache.port.host}:8080</port> </ports> <workingDir>/usr/src/http-cache-app</workingDir> <cmd> <shell>./start_http_cache.sh ${python.http.proxy.param} --python_proxyaddress=localhost:${docker.http-cache.port.host}</shell> </cmd> </run> </image> </configuration> <executions> <execution> <id>docker-start-for-it</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>docker-stop-for-it</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin>
Javascript
Maven properties
<sonar.javascript.lcov.reportPaths>${project.build.directory}/clamp-ui/coverage/lcov.info</sonar.javascript.lcov.reportPaths> <sonar.projectVersion>${project.version}</sonar.projectVersion> <sonar.sources>src/main,${project.build.directory}/clamp-ui/designer</sonar.sources> <sonar.exclusions>src/main/resources/**,**/clamp-ui/designer/lib/*</sonar.exclusions>
Python
Section TBD