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

时间:2014-04-03 22:12:43

标签: java unit-testing enums singleton testng

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

我使用枚举实现了单例。枚举有一个私有构造函数,在构造对象之前,我在其中进行了一些操作和验证。对于所有这些验证,我抛出一些异常(如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 个答案:

答案 0 :(得分:2)

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

一旦JVM尝试加载课程失败,它就不会再尝试加载它了。

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

答案 1 :(得分:1)

当ClassLoader加载类时,将静态初始化枚举常量INSTANCE。如果在第一次测试nullConfig()中失败,则该类被标记为“错误”,并且不会再次尝试静态初始化。

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

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