使用jMockit和Cobertura运行时,Log4j Logger.getLogger(Class)会引发NPE [英] Log4j Logger.getLogger(Class) throws NPE when running with jMockit and Cobertura

查看:68
本文介绍了使用jMockit和Cobertura运行时,Log4j Logger.getLogger(Class)会引发NPE的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have found a strange interaction between cobertura-maven-plugin 2.6 and jmockit 1.8. A particular pattern in our production code has a class with a lot of static methods that effectively wraps a different class that acts like a singleton. Writing unit tests for these classes went fine until I tried to run coverage reports with cobertura, when this error cropped up:

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().

This then leads to a NoClassDefFoundError and being unable to initialize the singleton class. Here's a full SSCCE (the shortest I can get it down to) that replicates the error; line 7 of MySingleton is 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;
    }

}

破坏一切的测试...

And the test that breaks everything...

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();

    }

}

还有行家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)一起运行时,它会抛出顶部显示的令人讨厌的一组异常.显然是某个地方的错误,但是是谁的?

To summarize the summary: When running an ordinary unit test (mvn clean test), the above tests just fine; when run with cobertura (mvn clean cobertura:cobertura) it throws the nasty set of exceptions shown at the top. Obviously a bug somewhere, but whose?

推荐答案

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

The cause for this problem isn't so much a bug, but a lack of robustness in JMockit when mocking a class that contains a static initializer. The next version of JMockit (1.9) will be improved on this point (I already have a working solution).

此外,如果Cobertura将其生成的方法(其中四个名称以"__cobertura_"开头的名称,添加到每个检测的类中)标记为"synthetic",则不会发生此问题,因此JMockit在模拟a时将忽略它们. Cobertura乐器类.无论如何,幸运的是,这是没有必要的.

Also, the problem would not have occurred if Cobertura marked its generated methods (four of them with names starting with "__cobertura_", added to every instrumented class) as "synthetic", so that JMockit would have ignored them when mocking a Cobertura-instrumented class. Anyway, fortunately this won't be necessary.

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

For now, there are two easy work-arounds which avoid the problem:

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

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

Both work-arounds prevent the NPE that would otherwise get thrown from inside the static class initializer, which is modified by Cobertura (to see these bytecode modifications, you can use the javap tool of the JDK, on classes under the target/generated-classes directory).

这篇关于使用jMockit和Cobertura运行时,Log4j Logger.getLogger(Class)会引发NPE的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆