2014-05-20 3 views
3

cobertura-maven-plugin 2.6과 jmockit 1.8 사이에 이상한 상호 작용이 있음을 발견했습니다. 프로덕션 코드의 특정 패턴에는 싱글 톤처럼 작동하는 다른 클래스를 효과적으로 래핑하는 많은 정적 메서드가 포함 된 클래스가 있습니다.Log4j Logger.getLogger (Class)가 jMockit 및 Cobertura를 실행할 때 NPE를 throw합니다.

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

    } 

} 

그리고 성명 :

<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)로 실행하면 맨 위에 표시된 불쾌한 예외 세트가 발생합니다. 분명히 어딘가에 버그가 있지만 누구입니까?

+0

하나의 세부 사항 : pom.xml 파일은 효과가 없으므로 플러그인을'pluginManagement' 섹션에 넣지 않아야합니다. –

+0

'mvn cobertura : cobertura'를 실행하고 명시 적으로 플러그인을 호출 할 때 적용되지 않습니까? 흥미 롭습니다 ... 좋아요,''내부에서 직접 시도해보고 도움이되는지 확인하겠습니다. – dcsohl

+0

아무런 차이가 없었습니다. – dcsohl

답변

3

이 문제의 원인은 정적 이니셜 라이저가 포함 된 클래스를 조롱 할 때 JMockit의 견고성이 부족하지만 버그가 아닙니다. 이 시점에서 JMockit (1.9)의 다음 버전이 개선 될 것입니다 (이미 작동하는 솔루션이 있습니다).

또한 Cobertura가 생성 된 메소드 ("instrumental 클래스"에 추가 된 "__cobertura_"로 시작하는 이름이있는 네 개의 메소드)를 "synthetic"로 표시하면 JMockit이 조롱 할 때 JMockit이 무시할 수 있도록 문제가 발생하지 않았습니다 Cobertura 계측 클래스. 어쨌든, 다행히도 이것은 필요하지 않습니다.

  1. 확실히 모든 클래스가 이미 시간 JVM에 의해 테스트 시작을 초기화 조롱 할 수 있는지 확인 :

    지금은

    , 문제가 발생하지 않도록이 쉬운 작업 차선책이있다. 인스턴스화하거나 정적 메서드를 호출하여이를 수행 할 수 있습니다.

  2. 모의 필드 또는 모의 매개 변수를 @Mocked(stubOutClassInitialization = true)으로 선언하십시오.

두 작업 어라운드 달리 Cobertura에 의해 수정되는 정적 클래스 초기화, 내부에서 던져지는 것 NPE 방지 (이 바이트 코드 수정을보고, 당신은 아래 클래스에 JDK의 javap 도구를 사용할 수 있습니다 target/generated-classes 디렉토리).

+0

나는 나를 위해 일할 수있는 해결 방법을 얻을 수 없었지만, JMockit 1.9가 문제를 해결해 주셔서 대단히 감사합니다! – dcsohl