首页 文章

通过Enum对Singleton进行单元测试会抛出java.lang.NoClassDefFoundError

提问于
浏览
1

这很奇怪,我不知道如何解释清楚 . 请耐心等待,并查看代码段以获取详细信息 .

我用enum实现了一个单例 . 枚举有一个私有构造函数,在构造对象之前,我在其中进行了一些操作和验证 . 对于所有这些验证,我抛出一些异常(如IllegalArgumentException) .

我的测试用例包括对负面和正面场景的测试 . 每当有超过2个测试用例混合了负面和正面测试用例时,我会得到以下异常: java.lang.NoClassDefFoundError: Could not initialize class com.blah.blah.SingletonClass . 请查看下面的完整代码 . 我正在使用以下技术堆栈:

JDK 1.7_51
Spring 4.0.0.RELEASE
testng 6.8.7

SingletonUsingEnum.java

public enum SingletonUsingEnum {
  INSTANCE;

  // Logger
  private final Logger logger = LoggerFactory.getLogger(SingletonUsingEnum.class);

  private SingletonUsingEnum() {
    final MyConfig myConfig = MyConfigManager.getMyConfig();
    if(myConfig == null) {
      throw new IllegalArgumentException("MyConfig is null");
    }

    if(StringUtils.isBlank(myConfig.getConfigValue())) {
      throw new IllegalArgumentException("myConfig.configValue value null/empty");
    }

    if(StringUtils.isBlank(myConfig.getOtherConfigValue())) {
      throw new IllegalArgumentException("myConfig.otherConfigValue null/empty");
    }

    logger.info("This is a singleton using enum");
  } 
}

MyConfig.java

public class MyConfig {
  private String configValue;
  private String otherConfigValue;

  public MyConfig(final String configValue, final String otherConfigValue) {
    this.configValue = configValue;
    this.otherConfigValue = otherConfigValue;
  }

  public String getConfigValue() {
    return configValue;
  }

  public String getOtherConfigValue() {
    return otherConfigValue;
  } 
}

MyConfigManager.java

public class MyConfigManager {
  private final static ConcurrentMap myConfigHolder = new ConcurrentHashMap();

  public static void registerMyConfig(final MyConfig myConfig) {
    myConfigHolder.put("myConfig", myConfig);
  }

  public static MyConfig getMyConfig() {
    return myConfigHolder.get("myConfig");
  }

  public static void clearAll() {
    myConfigHolder.clear();
  }
}

SingletonUsingEnumTest.java

@ContextConfiguration(classes = {SingletonUsingEnumTest.SpringConfig.class})
public class SingletonUsingEnumTest extends AbstractTestNGSpringContextTests {
  // Logger
  private static final Logger logger = LoggerFactory.getLogger(SingletonUsingEnumTest.class);

  @AfterMethod
  public void cleanUp() {
    MyConfigManager.clearAll();
  }

  @Test(expectedExceptions = {IllegalArgumentException.class, ExceptionInInitializerError.class})
  public void nullMyConfig() {
    MyConfigManager.registerMyConfig(new MyConfig("", ""));
    final SingletonUsingEnum singleton = SingletonUsingEnum.INSTANCE;
  }

  @Test
  public void validMyConfig_nullConfigValue() {
    MyConfigManager.registerMyConfig(new MyConfig("", "b"));
    final SingletonUsingEnum singleton = SingletonUsingEnum.INSTANCE;
  }

  @Test(dependsOnMethods = {"nullMyConfig"})
  public void allValidData() {
    MyConfigManager.registerMyConfig(new MyConfig("a", "b"));
    final SingletonUsingEnum singleton = SingletonUsingEnum.INSTANCE;
  }

  @Configuration
  @EnableAspectJAutoProxy(proxyTargetClass = true)
  public static class SpringConfig {

  }
}

测试执行的结果如下所示 . 我故意将 expectedExceptions 添加到第一个测试中,以隐藏第一个测试的堆栈跟踪 .

[TestNG] Running:
  /private/var/folders/l6/hmmqvjpj13ggmyc69sk1s5740000gn/T/testng-eclipse-135911498/testng-customsuite.xml

PASSED: nullMyConfig
FAILED: validMyConfig_nullConfigValue
java.lang.NoClassDefFoundError: Could not initialize class com.demo.singleton.SingletonUsingEnum
    at com.demo.singleton.SingletonUsingEnumTest.validMyConfig_nullConfigValue(SingletonUsingEnumTest.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:200)
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:157)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:212)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:707)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)

FAILED: allValidData
java.lang.NoClassDefFoundError: Could not initialize class com.demo.singleton.SingletonUsingEnum
    at com.demo.singleton.SingletonUsingEnumTest.allValidData(SingletonUsingEnumTest.java:44)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:200)
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:157)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:212)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:707)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)


===============================================
    Default test
    Tests run: 3, Failures: 2, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 3, Failures: 2, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@6997f7f4: 8 ms
[TestNG] Time taken by org.testng.reporters.XMLReporter@4474c7fe: 14 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 6 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@395e7bc4: 14 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter2@71419cf7: 5 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@6015eb5a: 31 ms

感谢有人能帮我解释发生了什么 .

谢谢,NN

2 回答

  • 2

    您的测试尝试使用您的单身人士 . 所以JVM加载并初始化枚举 . 作为此过程的一部分,它会调用构造函数来初始化唯一的枚举实例 . 构造函数抛出异常,阻止正确加载类 .

    一旦JVM尝试并且无法加载该类,它将不再尝试加载它 .

    Singleton是一种反模式 . 并且在加载类的时候急切地初始化的单例中执行这种过程会导致异常,这是一个非常糟糕的主意 .

  • 1

    当ClassLoader加载类时,将静态初始化枚举常量INSTANCE . 当你的第一个测试nullConfig()失败时,该类被标记为“错误”,并且第二次不尝试静态初始化 .

    作为一般规则,初始化枚举或单例所需的步骤应该是微不足道的并且是安全的 . 类加载因此静态初始化可能发生在您没有预料到的情况下,并且不应该触发任何应用程序代码运行 .

    如果您需要一些复杂的逻辑来读取配置数据以初始化您的单例,那么您可能应该将该类设置为可以根据需要使用不同配置进行实例化的常规类 .

相关问题