Log4j Logger.getLogger(Class)在使用jMockit和Cobertura运行时抛出NPE

时间:2014-05-20 13:24:21

标签: java maven junit cobertura jmockit

我发现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)运行时,它会抛出顶部显示的令人讨厌的异常集。显然是某个地方的错误,但是谁的?

1 个答案:

答案 0 :(得分:3)

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

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

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

  1. 确保在测试开始时JVM已经初始化了任何要模拟的类。这可以通过实例化或在其上调用静态方法来完成。
  2. 将模拟字段或模拟参数声明为@Mocked(stubOutClassInitialization = true)
  3. 这两种解决方法都会阻止NPE从静态类初始值设定项中抛出,而初始化程序由Cobertura修改(要查看这些字节码修改,您可以在类上使用JDK的javap工具在target/generated-classes目录下。