【问题标题】:Changed JaCoCo from offline to online, but still don't see coverage for classes in other modules将 JaCoCo 从离线更改为在线,但仍然看不到其他模块中的课程覆盖率
【发布时间】:2017-02-24 06:26:20
【问题描述】:

我有一个较大的 Maven 多模块项目构建。我开始使用 PowerMock 进行所有测试,并使用 Jacoco 0.7.8 和离线检测。

尽管这些被称为“单元测试”,但一个模块中的类测试确实调用了其他模块中的重要代码。因此,当我们查看生成的报告时,我们会看到与测试相同的模块中的类的覆盖率,但我们看不到在测试期间执行的其他模块中的类的覆盖率。

我的假设是,如果我能够将其更改为使用在线检测,则生成的覆盖率报告将包括当前模块之外的其他模块的类。

因此,我着手修复我们的 CUT(被测类)中需要使用 PowerMock 而不是 Mockito 的一个细节(那部分非常简单)。我在每个测试和相应的 CUT 中一一修复了这个问题,但现在我只在较大版本的单个模块中进行了这些更改。我验证了测试工作正常,并且我可以看到 EclEmma 的交互式覆盖(PowerMock 的另一个限制)。

jacoco-maven-plugin 在所有子模块使用的父 pom 中配置,目前使用离线检测。最终,我将更改此父 pom 中的配置以使用在线检测,但我决定暂时将其保留在那里并覆盖我已完成转换的每个模块中的配置,以便该模块使用在线仪器。我相信我已经“正确”地做到了这一点,尽管我不得不使用 hack 来覆盖子模块中的执行列表(请记住,一旦它们全部转换,我将删除它们)。

当我运行构建(“clean install”)时,我看到它执行“prepare-agent”,打印生成的“argLine”值,然后稍后执行surefire,然后是“report”目标执行,打印jacoco.exec 文件的路径,我看到它说“Analyzed bundle 'my-module-name' with 1 classes”。这似乎表明存在问题。

构建完成后,我在浏览器中打开“target/site/jacoco/index.html”文件,发现它只包含当前模块的覆盖范围,并且只包含单个类。

同样好奇的是,我表面上检查了生成的“jacoco.exec”文件。我注意到它肯定比我从其他仍在使用离线仪器的模块中看到的要大。我无法解释文件的格式,但我确实尝试简单地“cat”文件,只是为了看看我能看到什么。我在我的测试正在执行的其他模块中看到了代表类名的字符串。

因此,在线检测似乎至少记录了其他模块中类的数据,但生成的报告并没有显示出来。

是否有可能获得此覆盖范围?

以下是“有效pom”输出的摘录:

  <plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.7.8</version>
    <executions>
      <execution>
        <id>default-instrument</id>
        <phase>none</phase>
        <goals>
          <goal>instrument</goal>
        </goals>
      </execution>
      <execution>
        <id>default-restore-instrumented-classes</id>
        <phase>none</phase>
        <goals>
          <goal>restore-instrumented-classes</goal>
        </goals>
      </execution>
      <execution>
        <id>default-report</id>
        <phase>prepare-package</phase>
        <goals>
          <goal>report</goal>
        </goals>
      </execution>
      <execution>
        <id>prepare-agent</id>
        <goals>
          <goal>prepare-agent</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
  <plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19.1</version>
    <executions>
      <execution>
        <id>default-test</id>
        <phase>test</phase>
        <goals>
          <goal>test</goal>
        </goals>
        <configuration>
          <argLine>@{argLine} -Xmx1024m</argLine>
          <includes>
            <include>**/*Test.java</include>
          </includes>
          <systemPropertyVariables>
            <running-unit-test>true</running-unit-test>
            <jacoco-agent.destfile>...\target/jacoco.exec</jacoco-agent.destfile>
          </systemPropertyVariables>
        </configuration>
      </execution>
    </executions>
    <configuration>
      <argLine>@{argLine} -Xmx1024m</argLine>
      <includes>
        <include>**/*Test.java</include>
      </includes>
      <systemPropertyVariables>
        <running-unit-test>true</running-unit-test>
        <jacoco-agent.destfile>...\target/jacoco.exec</jacoco-agent.destfile>
      </systemPropertyVariables>
    </configuration>
  </plugin>

请注意,用于离线检测的执行具有阶段“none”,因此它们没有被使用,我还设置了“jacoco-agent.destfile”属性,该属性仅用于离线检测。

【问题讨论】:

    标签: maven jacoco jacoco-maven-plugin


    【解决方案1】:

    Goal report 仅对当前模块中的类进行分析,并创建仅包含它们的报告,即使记录了其他类的数据。还有goal report-aggregate

    reactor 内多个项目的覆盖率报告

    (见下文示例)

    同样回到 PowerMockito - 正如https://stackoverflow.com/a/42305077/244993 中所说,它增加了额外的复杂性,但是:

    a/src/main/java/example/A.java:

    package example;
    
    class A {
      // to be tested in module "a"
      void a() {
        System.out.println("A_a" + fun());
      }
    
      // to be tested in module "b"
      void b() {
        System.out.println("A_b" + fun());
      }
    
      // to be mocked by PowerMockito
      static String fun() {
        return "";
      }
    }
    

    a/src/test/java/example/ATest.java:

    package example;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mockito;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    
    @PrepareForTest({A.class})
    @RunWith(PowerMockRunner.class)
    public class ATest {
      @Test
      public void test() {
        PowerMockito.mockStatic(A.class);
        Mockito.when(A.fun()).thenReturn("fun");
    
        new A().a();
      }
    }
    

    b/src/main/java/example/B.java:

    package example;
    
    class B {
      // to be tested in module "b"
      void b() {
        System.out.println("B_b" + fun());
      }
    
      // to be mocked by PowerMockito
      static String fun() {
        return "";
      }
    }
    

    b/src/test/java/example/BTest.java:

    package example;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mockito;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    
    @PrepareForTest({A.class, B.class})
    @RunWith(PowerMockRunner.class)
    public class BTest {
      @Test
      public void test() {
        PowerMockito.mockStatic(A.class);
        Mockito.when(A.fun()).thenReturn("fun");
    
        PowerMockito.mockStatic(B.class);
        Mockito.when(B.fun()).thenReturn("fun");
    
        new A().b();
        new B().b();
      }
    }
    

    a/pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <parent>
        <groupId>org.example</groupId>
        <artifactId>example</artifactId>
        <version>1.0-SNAPSHOT</version>
      </parent>
    
      <artifactId>a</artifactId>
    </project>
    

    b/pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <parent>
        <groupId>org.example</groupId>
        <artifactId>example</artifactId>
        <version>1.0-SNAPSHOT</version>
      </parent>
    
      <dependencies>
        <dependency>
          <groupId>org.example</groupId>
          <artifactId>a</artifactId>
          <version>${project.version}</version>
        </dependency>
      </dependencies>
    
      <artifactId>b</artifactId>
    </project>
    

    report/pom.xml 用于聚合跨模块的覆盖率:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <parent>
        <groupId>org.example</groupId>
        <artifactId>example</artifactId>
        <version>1.0-SNAPSHOT</version>
      </parent>
    
      <artifactId>report</artifactId>
      <packaging>pom</packaging>
    
      <dependencies>
        <dependency>
          <groupId>org.example</groupId>
          <artifactId>a</artifactId>
          <version>${project.version}</version>
        </dependency>
        <dependency>
          <groupId>org.example</groupId>
          <artifactId>b</artifactId>
          <version>${project.version}</version>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <executions>
              <execution>
                <id>report</id>
                <phase>prepare-package</phase>
                <goals>
                  <goal>report-aggregate</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
    

    最后是最重要的部分 - pom.xml 所有魔法发生在哪里:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
      <groupId>org.example</groupId>
      <artifactId>example</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>pom</packaging>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    
        <jacoco.version>0.7.9</jacoco.version>
        <powermock.version>1.6.6</powermock.version>
      </properties>
    
      <modules>
        <module>a</module>
        <module>b</module>
      </modules>
    
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.8.2</version>
          <scope>test</scope>
        </dependency>
    
        <dependency>
          <groupId>org.powermock</groupId>
          <artifactId>powermock-module-junit4</artifactId>
          <version>${powermock.version}</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.powermock</groupId>
          <artifactId>powermock-api-mockito</artifactId>
          <version>${powermock.version}</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.0.2</version>
          </plugin>
        </plugins>
      </build>
    
      <profiles>
        <profile>
          <id>coverage</id>
          <modules>
            <module>report</module>
          </modules>
          <dependencies>
            <dependency>
              <groupId>org.jacoco</groupId>
              <artifactId>org.jacoco.agent</artifactId>
              <classifier>runtime</classifier>
              <version>${jacoco.version}</version>
              <scope>test</scope>
            </dependency>
          </dependencies>
          <build>
            <plugins>
              <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                  <execution>
                    <id>instrument</id>
                    <phase>process-classes</phase>
                    <goals>
                      <goal>instrument</goal>
                    </goals>
                  </execution>
                  <execution>
                    <id>report</id>
                    <!--
                    restore original classes before generation of report,
                    but after packaging of JAR:
                    -->
                    <phase>post-integration-test</phase>
                    <goals>
                      <goal>restore-instrumented-classes</goal>
                      <goal>report</goal>
                    </goals>
                  </execution>
                </executions>
              </plugin>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.2</version>
                <configuration>
                  <systemPropertyVariables>
                    <jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
                  </systemPropertyVariables>
                </configuration>
              </plugin>
            </plugins>
          </build>
        </profile>
      </profiles>
    </project>
    

    在这样的设置中

    mvn clean verify -Pcoverage
    

    会生成

    • a/target/site/jacoco/index.html 包含模块 a 中的类的覆盖率,模块 a 中的测试
    • b/target/site/jacoco/index.html 包含模块 b 中的类的覆盖率,模块 b 中的测试
    • report/target/site/jacoco-aggregate/index.html 包含模块 ab 模块中的类的覆盖率 ab 模块中的测试

    但是生成的 JAR 文件包含检测类,但是

    mvn package -DskiptTests -Dmaven.jar.forceCreation
    

    将生成非仪器化的 JAR。

    如果您摆脱 PowerMockito,则不需要这种复杂性。在这种情况下,pom.xml 中的 coverage 配置文件可以简化为:

    <profile>
      <id>coverage</id>
      <modules>
        <module>report</module>
      </modules>
      <build>
        <plugins>
          <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>${jacoco.version}</version>
            <executions>
              <execution>
                <goals>
                  <goal>prepare-agent</goal>
                  <goal>report</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
    

    mvn clean verify -Pcoverage 将生成不需要重建的相同报告和 JAR。

    【讨论】:

    • 我真正需要了解的是,假设一个模块的所有测试都是 mockito,并且使用 jacoco 在线检测,模块的测试如何显示模块外的类的覆盖率?我假设当我使用“合并”目标来生成合并的 jacoco.exec 文件时,我的 sonarqube 输出将具有所有覆盖范围,但我希望能够在生成的 jacoco 报告中看到该覆盖范围。有什么办法可以得到吗?
    • @DavidM.Karr 到目前为止,在您的问题描述中没有一个词,也没有关于“SonarQube”,也没有关于“合并”。正如已经说过的few times - 请努力提供complete example 以获得与您的情况完全匹配的答案。顺便说一句,您是否尝试在合并文件上执行“报告聚合”?
    • 我没有提到sonarqube,因为我对它没有任何问题。据我所知,它从合并的 exec 文件中获取数据,该文件在每种情况下都包含调用模块之外的类的数据。是的,我还使用了报告聚合,我在这篇文章中没有提到,因为我关注的是每个模块的构建,而不是聚合,但我也注意到报告聚合输出也缺少覆盖从模块外部到达的类。在每种情况下,覆盖范围都是“浅”的。
    • @DavidM.Karr 好的。为了能够帮助你——如何重现这种行为?
    • 不知道。我想我将不得不花时间尝试构建一个可重现的测试用例。这将需要很长时间,我什至不知道我是否能够做到。如果我可以在 jacoco 中打开任何可能提供线索的诊断程序,这可能会很有用。此时,我可以看到我在 EclEmma 中得到了完全覆盖,如果主分支上生成的构建在 SonarQube 中显示完全覆盖,我可能无法证明构建这个可重现测试用例的时间是合理的。
    猜你喜欢
    • 2014-10-18
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多