Measuring the coverage of your tests

アプリケーションのテストカバレッジを測定する方法をご紹介します。このガイドでは、以下の内容をカバーしています:

  • ユニットテストのカバレッジを測定する

  • 統合テストのカバレッジを測定する

  • ユニットテストと統合テストの実行を分離する

  • 全テストのカバレッジを統合する

ネイティブモードではコードカバレッジはサポートされていませんのでご注意ください。

1. 前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.6

  • 使用したい場合は、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

  • アプリケーションのテストガイド が完了済であること

2. アーキテクチャ

このガイドで構築されたアプリケーションは、サービスを使用するために依存性インジェクションに依存した JAX-RS エンドポイント (hello world) に過ぎません。サービスは JUnit 5 でテストされ、エンドポイントには @QuarkusTest のアノテーションが付けられます。

3. ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。しかし、完成した例にすぐに進むことができます。Git リポジトリをクローンします: git clone https://github.com/quarkusio/quarkus-quickstarts.git 、または archive をダウンロードします。

ソリューションは tests-with-coverage-quickstart directory にあります。

4. 簡単なプロジェクトと2つのテストから始める

Quarkus Mavenプラグインで作成した空のアプリケーションから始めてみましょう:

コマンドラインインタフェース
quarkus create app org.acme:tests-with-coverage-quickstart \
    --stream=2.16 \
    --extension='resteasy-reactive' \
    --no-code
cd tests-with-coverage-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストール方法や使用方法については、 Quarkus CLIガイド を参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.16.12.Final:create \
    -DplatformVersion=2.16 \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=tests-with-coverage-quickstart \
    -Dextensions='resteasy-reactive' \
    -DnoCode
cd tests-with-coverage-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

ここで、アプリケーションをテストで適切にカバーするために必要な要素をすべて追加していきます。

最初に、helloエンドポイントを提供するアプリケーション:

package org.acme.testcoverage;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    private final GreetingService service;

    @Inject
    public GreetingResource(GreetingService service) {
        this.service = service;
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/greeting/{name}")
    public String greeting(String name) {
        return service.greeting(name);
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

このエンドポイントは、グリーティングサービスを利用しています:

package org.acme.testcoverage;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {

    public String greeting(String name) {
        return "hello " + name;
    }

}

また、プロジェクトにはテストも必要です:

package org.acme.testcoverage;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;

import java.util.UUID;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/hello")
          .then()
             .statusCode(200)
             .body(is("hello"));
    }

    @Test
    public void testGreetingEndpoint() {
        String uuid = UUID.randomUUID().toString();
        given()
          .pathParam("name", uuid)
          .when().get("/hello/greeting/{name}")
          .then()
            .statusCode(200)
            .body(is("hello " + uuid));
    }
}

5. Jacocoの設定

次に、Jacocoをプロジェクトに追加する必要があります。そのために、ビルドファイルに以下を追加する必要があります:

pom.xml
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-jacoco</artifactId>
  <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-jacoco")

このQuarkusエクステンションは、通常はJacoco Mavenプラグインで行われることすべての処理を行うため、追加の設定は必要ありません。

Using both the extension and the plugin requires special configuration, if you add both you will get lots of errors about classes already being instrumented. The configuration needed is detailed below.

6. カバレッジ付きテストの実行

mvn verify を実行すると、テストが実行され、その結果が target/jacoco-reports に出力されます。 quarkus-jacoco のエクステンションを使えば、これだけでJacoco はすぐに動作します。

これにはいくつかの設定オプションがあります:

ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能

Configuration property

タイプ

デフォルト

The jacoco data file

Environment variable: QUARKUS_JACOCO_DATA_FILE

Show more

string

jacoco-quarkus.exec

Whether to reuse (true) or delete (false) the jacoco data file on each run.

Environment variable: QUARKUS_JACOCO_REUSE_DATA_FILE

Show more

boolean

false

If Quarkus should generate the Jacoco report

Environment variable: QUARKUS_JACOCO_REPORT

Show more

boolean

true

Encoding of the generated reports.

Environment variable: QUARKUS_JACOCO_OUTPUT_ENCODING

Show more

string

UTF-8

Name of the root node HTML report pages.

Environment variable: QUARKUS_JACOCO_TITLE

Show more

string

Footer text used in HTML report pages.

Environment variable: QUARKUS_JACOCO_FOOTER

Show more

string

Encoding of the source files.

Environment variable: QUARKUS_JACOCO_SOURCE_ENCODING

Show more

string

UTF-8

A list of class files to include in the report. May use wildcard characters (* and ?). When not specified everything will be included. For instance: - **/fo/**/* targets all classes under fo and sub packages - **/bar/* targets all classes directly under bar - **/*BAR*.class targets classes that contain BAR in their name regardless of path

Environment variable: QUARKUS_JACOCO_INCLUDES

Show more

文字列のリスト

**

A list of class files to exclude from the report. May use wildcard characters (* and ?). When not specified nothing will be excluded. For instance: - **/fo/**/* targets all classes under fo and sub packages - **/bar/* targets all classes directly under bar - **/*BAR*.class targets classes that contain BAR in their name regardless of path

Environment variable: QUARKUS_JACOCO_EXCLUDES

Show more

文字列のリスト

The location of the report files.

Environment variable: QUARKUS_JACOCO_REPORT_LOCATION

Show more

string

jacoco-report

7. QuarkusTestを使用していないテストのカバレッジ

Quarkusの自動Jacoco設定は、 @QuarkusTest でアノテーションされたテストに対してのみ機能します。他のテストのカバレッジもチェックしたい場合は、Jacoco mavenプラグインを利用する必要があります。

quarkus-jacoco エクステンションを pom.xml に含めるのに加えて、以下のような設定が必要になります:

pom.xml
<project>
    <build>
        <plugins>
            ...
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                   <execution>
                      <id>default-prepare-agent</id>
                      <goals>
                           <goal>prepare-agent</goal>
                      </goals>
                      <configuration>
                        <exclClassLoaders>*QuarkusClassLoader</exclClassLoaders>  (1)
                        <destFile>${project.build.directory}/jacoco-quarkus.exec</destFile>
                        <append>true</append>
                      </configuration>
                   </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
1 この設定は、 QuarkusClassLoader によってロードされる @QuarkusTest 関連のクラスを無視するようになっています
build.gradle
plugins {
    id 'jacoco' (1)
}

test {
    finalizedBy jacocoTestReport
    jacoco {
        excludeClassLoaders = ["*QuarkusClassLoader"] (2)
        destinationFile = layout.buildDirectory.file("jacoco-quarkus.exec").get().asFile (2)
    }
    jacocoTestReport.enabled = false (3)
}
1 jacoco gradleプラグインの追加
2 この設定は、 QuarkusClassLoader によってロードされる @QuarkusTest 関連のクラスを無視するようになっています
3 quarkus-jacoco エクステンションも使用していて、少なくとも 1 つの @QuarkusTest がある場合は、この設定を false にします。デフォルトの jacocoTestReport タスクは省略できます。なぜなら、通常のユニットテストと @QuarkusTest クラスの実行データが同じファイルに記録されているため、 quarkus-jacoco がそれらを合わせたレポートを生成するからです。
This config will only work if at least one @QuarkusTest is being run. If you are not using @QuarkusTest then you can simply use the JaCoCo plugin in the standard manner with no additional config.

7.1. 結合テストのカバレッジ

結合テストからコードカバレッジデータを取得するには、以下の要件を満たす必要があります:

  • ビルドされたアーティファクトがjarであること(コンテナやネイティブバイナリではないこと)。

  • Jacocoがビルドツールで設定されていること。

  • アプリケーションは、 quarkus.package.write-transformed-bytecode-to-build-output が true に設定してビルドされていること

Setting quarkus.package.write-transformed-bytecode-to-build-output=true should be done with caution and only if subsequent builds are done in a clean environment - i.e. the build tool’s output directory has been completely cleaned.

pom.xml で、Jacocoのプラグイン設定を以下のように追加します。これにより、結合テストのデータをユニットテストと同じ保存先のファイルに追加し、結合テストが完了した後にjacocoレポートを再構築することで、包括的なコードカバレッジレポートを作成します。

<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>${jacoco.version}</version>
            <executions>
                ... (1)

                <execution>
                    <id>default-prepare-agent-integration</id>
                    <goals>
                        <goal>prepare-agent-integration</goal>
                    </goals>
                    <configuration>
                        <destFile>${project.build.directory}/jacoco-quarkus.exec</destFile>
                        <append>true</append>
                    </configuration>
                </execution>
                <execution>
                    <id>report</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                    <configuration>
                        <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
                        <outputDirectory>${project.build.directory}/jacoco-report</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
    ...
</build>
1 すべてのexecutionsは同じ <plugin> の定義にあるはずなので、すべてつなげてください。

Jacocoエージェントで結合テストをjarとして実行するには、 pom.xml に以下を追加します。

<build>
    ...
    <plugins>
        ...
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                    <configuration>
                        <systemPropertyVariables>
                            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                            <maven.home>${maven.home}</maven.home>
                            <quarkus.test.arg-line>${argLine}</quarkus.test.arg-line>
                        </systemPropertyVariables>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
    ...
</build>
Sharing the same value for quarkus.test.arg-line might break integration test runs that test different types of Quarkus artifacts. In such cases, the use of Maven profiles is advised.

8. カバレッジの閾値を設定

JaCoCo Maven プラグインを使用して、コードカバレッジの閾値を設定することができます。注意点としては、 <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile> があり、 quarkus.jacoco.data-file の設定と一致するように設定が必要です。

pom.xml
<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>${jacoco.version}</version>
            <executions>
                ... (1)

                <execution>
                    <id>jacoco-check</id>
                    <goals>
                        <goal>check</goal>
                    </goals>
                    <phase>post-integration-test</phase>
                    <configuration>
                        <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
                        <rules>
                            <rule>
                                <element>BUNDLE</element>
                                <limits>
                                    <limit>
                                        <counter>LINE</counter>
                                        <value>COVEREDRATIO</value>
                                        <minimum>0.8</minimum>
                                    </limit>
                                    <limit>
                                        <counter>BRANCH</counter>
                                        <value>COVEREDRATIO</value>
                                        <minimum>0.72</minimum>
                                    </limit>
                                </limits>
                            </rule>
                        </rules>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
    ...
</build>
1 すべてのexecutionsは同じ <plugin> の定義にあるはずなので、すべてつなげてください。
build.gradle
jacocoTestCoverageVerification {
    executionData.setFrom("$project.buildDir/jacoco-quarkus.exec")
    violationRules {
        rule {
            limit {
                counter = 'INSTRUCTION'
                value = 'COVEREDRATIO'
                minimum = 0.80
            }
            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.72
            }
        }
    }
}
check.dependsOn jacocoTestCoverageVerification

検証タスクからクラスを除外するには、以下のように設定します:

jacocoTestCoverageVerification {
    afterEvaluate { (1)
        classDirectories.setFrom(files(classDirectories.files.collect { (2)
            fileTree(dir: it, exclude: [
                    "org/example/package/**/*" (3)
            ])
        }))
    }
}
1 Gradle では、評価フェーズの後に classDirectories を読み込む必要があります
2 現在、GradleのJaCoCoには、 excludes をこのように指定する必要があります。 - https://github.com/gradle/gradle/issues/14760 の問題が修正されると、除外されます
3 org/example/package パッケージに含まれるすべてのクラスを除外します

9. まとめ

これで、テストのカバレッジを研究するために必要なすべての情報を手に入れることができました!しかし、カバーされていないコードは、確かに十分にテストされていないものがありますが、カバーされているコードの中には、必ずしも 十分に テストされていないものもあることを覚えておいてください。良いテストを書くようにしましょう!