【问题标题】:Log4j Logger.getLogger(Class) throws NPE when running with jMockit and Cobertura与 jMockit 和 Cobertura 一起运行时,Log4j Logger.getLogger(Class) 抛出 NPE
【发布时间】:2014-05-20 13:24:20
【问题描述】:

我发现 cobertura-maven-plugin 2.6 和 jmockit 1.8 之间存在奇怪的交互。我们生产代码中的一个特定模式有一个包含许多静态方法的类,这些方法有效地包装了一个像单例一样的不同类。为这些类编写单元测试一直很好,直到我尝试使用 cobertura 运行覆盖率报告时,出现了这个错误:

java.lang.ExceptionInInitializerError
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)
Caused by: java.lang.NullPointerException
    at com.example.foo.MySingleton.<clinit>(MySingleton.java:7)
    ... 13 more

这会导致NoClassDefFoundError 并且无法初始化单例类。这是复制错误的完整 SSCCE(我能得到的最短的); MySingleton 的第 7 行是 Logger.getLogger()

这里是“单例”...

package com.example.foo;

import org.apache.log4j.Logger;

public class MySingleton {

    private static final Logger LOG = Logger.getLogger(MySingleton.class);

    private boolean inited = false;
    private Double d;

    MySingleton() {
    }

    public boolean isInited() {
        return inited;
    }

    public void start() {
        inited = true;
    }

    public double getD() {
        return d;
        }
}

还有静态类...

package com.example.foo;

import org.apache.log4j.Logger;

public class MyStatic {

    private static final Logger LOGGER = Logger.getLogger(MyStatic.class);

    private static MySingleton u = new MySingleton();

    public static double getD() {
        if (u.isInited()) {
            return u.getD();
        }
        return 0.0;
    }

}

以及打破一切的测试......

package com.example.foo;

import mockit.Expectations;
import mockit.Mocked;
import mockit.Tested;

import org.junit.Test;

public class MyStaticTest {

    @Tested MyStatic myStatic;

    @Mocked MySingleton single;

    @Test
    public void testThatBombs() {
        new Expectations() {{
            single.isInited(); result = true;
            single.getD(); /*result = 1.2;*/
        }};

//        Deencapsulation.invoke(MyStatic.class, "getD");
        MyStatic.getD();

    }

}

还有 maven pom:

<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>
    <groupId>com.example.foo</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Test</name>

    <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>

        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.8</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>cobertura-maven-plugin</artifactId>
                    <version>2.6</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

总结总结:运行普通单元测试(mvn clean test),上面的测试就好了;当使用 cobertura (mvn clean cobertura:cobertura) 运行时,它会抛出顶部显示的一组讨厌的异常。显然是某个地方的错误,但是谁的?

【问题讨论】:

  • 一个细节:pom.xml 文件必须将插件放在pluginManagement 部分,因为它不会生效。
  • 运行mvn cobertura:cobertura显式调用插件不生效?有趣...好的,我会直接在&lt;plugins/&gt; 中尝试,看看是否有帮助。
  • 这没什么区别,很抱歉。
  • 我知道,但最好编辑问题以修复 pom.xml 文件。至于NPE的由来,后面我会写一个答案。

标签: java maven junit cobertura jmockit


【解决方案1】:

这个问题的原因与其说是错误,不如说是在模拟包含静态初始化程序的类时 JMockit 缺乏健壮性。下一个版本的 JMockit (1.9) 将在这一点上进行改进(我已经有了一个可行的解决方案)。

此外,如果 Cobertura 将其生成的方法(其中四个名称以“__cobertura_”开头,添加到每个检测类)标记为“合成”,则问题不会发生,这样 JMockit 在模拟时会忽略它们Cobertura 仪表类。无论如何,幸运的是这不是必需的。

目前,有两种简单的解决方法可以避免该问题:

  1. 确保在测试开始时任何要模拟的类都已被 JVM 初始化。这可以通过实例化它或在其上调用静态方法来完成。
  2. 将模拟字段或模拟参数声明为@Mocked(stubOutClassInitialization = true)

这两种解决方法都可以防止 NPE 从静态类初始化程序中抛出,该初始化程序由 Cobertura 修改(要查看这些字节码修改,您可以使用 JDK 的 javap 工具,在target/generated-classes 目录)。

【讨论】:

  • 我一直无法找到适合我的解决方法,但 JMockit 1.9 解决了这个问题,非常感谢!
猜你喜欢
  • 1970-01-01
  • 2014-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-26
  • 2022-01-18
  • 1970-01-01
  • 2021-10-13
相关资源
最近更新 更多