The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

テストカバレッジの測定

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

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

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

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

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

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

1. 前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.9.6

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

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

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

2. アーキテクチャ

このガイドで作成するアプリケーションは、サービスを使用するために依存性の注入を使用した Jakarta REST エンドポイント (hello world) です。サービスは JUnit 5 でテストされ、エンドポイントには @QuarkusTest のアノテーションが付けられます。

3. ソリューション

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

ソリューションは tests-with-coverage-quickstart ディレクトリ にあります。

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

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

コマンドラインインタフェース
quarkus create app org.acme:tests-with-coverage-quickstart \
    --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:3.8.3:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=tests-with-coverage-quickstart \
    -Dextensions='resteasy-reactive' \
    -DnoCode
cd tests-with-coverage-quickstart

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

Windowsユーザーの場合:

  • cmdを使用する場合、(バックスラッシュ \ を使用せず、すべてを同じ行に書かないでください)。

  • Powershellを使用する場合は、 -D パラメータを二重引用符で囲んでください。例: "-DprojectArtifactId=tests-with-coverage-quickstart"

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

まず、helloエンドポイントを提供するJakarta RESTリソース:

package org.acme.testcoverage;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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 jakarta.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プラグインで行われることすべての処理を行うため、追加の設定は必要ありません。

エクステンションとプラグインの両方を使用するには、特別な設定が必要です。両方を追加すると、すでにインスツルメンテーションされているクラスに関する多くのエラーが発生します。必要な設定の詳細は以下のとおりです。

6. マルチモジュールプロジェクトでの作業

3.2 までは、 data-filereport-location は常にモジュールのビルド出力ディレクトリからの相対パスでした。このため、すべてのカバレッジを1つの親ディレクトリに集約したいようなマルチモジュールプロジェクトでは作業できませんでした。 3.3 から、 data-file または report-location を指定すると、そのパスがそのまま使われます。以下は surefire プラグインの設定例です:

<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <systemPropertyVariables>
      <quarkus.jacoco.data-file>${maven.multiModuleProjectDirectory}/target/jacoco.exec</quarkus.jacoco.data-file>
      <quarkus.jacoco.reuse-data-file>true</quarkus.jacoco.reuse-data-file>
      <quarkus.jacoco.report-location>${maven.multiModuleProjectDirectory}/target/coverage</quarkus.jacoco.report-location>
    </systemPropertyVariables>
  </configuration>
</plugin

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

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

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

ビルド時に固定される設定プロパティ - その他の設定プロパティは実行時にオーバーライド可能です。

Configuration property

デフォルト

Whether or not the jacoco extension is enabled.

Environment variable: QUARKUS_JACOCO_ENABLED

Show more

boolean

true

The jacoco data file. The path can be relative (to the module) or absolute.

Environment variable: QUARKUS_JACOCO_DATA_FILE

Show more

string

target/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

list of string

**

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

list of string

The location of the report files. The path can be relative (to the module) or absolute.

Environment variable: QUARKUS_JACOCO_REPORT_LOCATION

Show more

string

target/jacoco-report

マルチモジュールのプロジェクトを扱う場合、コードカバレッジが適切に機能するためには、上流のモジュールが適切に インデックスされている 必要があります。

8. 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 がそれらを合わせたレポートを生成するからです。
この設定は、少なくとも1つの @QuarkusTest が実行されている場合にのみ機能します。 @QuarkusTest を使用していない場合は、追加の設定をすることなく、通常の方法で Jacoco プラグインを使用することができます。

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

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

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

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

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

quarkus.package.write-transformed-bytecode-to-build-output=true は慎重に設定を行う必要があります。 後続のビルドがクリーンな環境で行われる場合、つまりビルドツールの出力ディレクトリが完全にクリーンである場合のみ行う必要があります。

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>
quarkus.test.arg-line で同じ値を共有すると、異なるタイプの Quarkus アーティファクトをテストする統合テストの実行が中断される場合があります。このような場合は、Mavenプロファイルの使用することをお勧めします。

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

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 パッケージに含まれるすべてのクラスを除外します

10. まとめ

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

関連コンテンツ