2014-05-20 56 views
3

我發現了cobertura-maven-plugin 2.6和jmockit 1.8之間的奇怪交互。我們的產品代碼中的一個特定模式有一個包含很多靜態方法的類,它們有效地包裝了一個類似單例的不同類。Log4j Logger.getLogger(Class)在使用jMockit和Cobertura運行時引發NPE

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並且是無法初始化單例類:直到我試圖運行用Cobertura覆蓋報告,當這個錯誤出現了這些類編寫單元測試很順利。這裏有一個完整的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)一起運行時,它會拋出頂部顯示的一系列令人討厭的異常。很明顯,某個地方有一個bug,但是誰的?

+0

一個細節:pom.xml文件必須*不*將插件放在'pluginManagement'部分中,因爲它不會生效。 –

+0

運行'mvn cobertura:cobertura'並顯式調用插件時它不會生效?有趣......好吧,我會直接在''內試試看看是否有幫助。 – dcsohl

+0

這沒什麼區別,很遺憾地說。 – dcsohl

回答

3

這個問題的原因並不是一個錯誤,而是在模擬包含靜態初始化器的類時,JMockit缺乏健壯性。 JMockit(1.9)的下一個版本將在這一點上得到改進(我已經有了一個可行的解決方案)。此外,如果Cobertura將其生成的方法(其中四個名稱以「__cobertura_」開頭,將其添加到每個已檢測類中)標記爲「合成」,則不會發生問題,因此JMockit在嘲笑時會忽略它們一個Cobertura儀器類。無論如何,幸運的是這不是必要的。

現在,有兩個簡單的變通辦法,其避免這個問題:

  1. 確保被嘲笑任何一類已初始化JVM所用時間的考驗開始。這可以通過實例化或調用靜態方法來完成。
  2. 聲明模擬字段或模擬參數爲@Mocked(stubOutClassInitialization = true)

兩種變通辦法阻止,否則將獲得由靜態類初始化,這是由的Cobertura修改裏面拋出的NPE(看到這些字節碼修改,則可以使用javap工具的JDK,在班下目錄target/generated-classes)。

+0

我從來沒有能夠得到解決方法爲我工作,但JMockit 1.9解決了這個問題,所以非常感謝你! – dcsohl

相關問題