基于枚举的单例实现和单元测试,将状态保持为枚举的副作用

时间:2014-04-21 21:11:24

标签: java junit enums singleton

正如许多人所说,单身人士很糟糕,这是一种“反模式”。幸运与否,我使用了一些具有它的代码,它不会在不久的将来消失。所以我的问题如下。

具有州的单身人士:

import java.util.HashMap;
import java.util.Map;

public enum Singleton {
    INSTANCE;

    private final Map<String, String> _mapper = new HashMap<String, String>();

    public String getValue(String key) {
        return _mapper.get(key);
    }

    public void addEntry(String key, String val) {
        _mapper.put(key, val);
    }

    public String removeKey(String key) {
        return _mapper.remove(key);
    }
}

非常愚蠢的测试,没有考虑在测试运行中保留单例INSTANCE状态:

import org.junit.Assert;
import org.junit.Test;

public class SingletonTest {

    public static final String KEY_1 = "key_1";

    @Test public void testOne() {
        Singleton.INSTANCE.addEntry(KEY_1, "val_1");
        Singleton.INSTANCE.addEntry("key_2", "val_2");
    }

    @Test public void testTwo() {
        Assert.assertNull(Singleton.INSTANCE.getValue(KEY_1));
    }
}

如何在测试运行之间清理状态?可以用JUnit Runners完成吗?

对象的实际状态更复杂,并通过构造函数初始化。我正在寻找类似的东西:http://powermock.googlecode.com/svn/docs/powermock-1.3.5/apidocs/org/powermock/core/classloader/annotations/PrepareForTest.html

3 个答案:

答案 0 :(得分:1)

您可以使用在执行每个测试后执行的@After修饰的方法。在此方法中,您可以清除枚举中的地图状态:

@After
public void clear() {
    Singleton.INSTANCE.removeKey(KEY_1);
    Singleton.INSTANCE.removeKey("key_2");
    //and on and on...
}

当然,更好的方法是在clear中使用enum方法清除地图的值,这样@After方法的主体就会缩短:

@After
public void clear() {
    //assuming you can create such method
    Singleton.INSTANCE.clearEntries();
}

答案 1 :(得分:0)

一种选择是在枚举上包含一个包私有clear方法。这并不是完全万无一失的,但是这种方法有望变得足够简单,以至于它完全没有错误。在您的示例中,它只需调用_mapper.clear()

另一种选择是使用与Delegate相同的方法创建一个类Singleton,并让Singleton只委托给该类的实例。然后,您测试Delegate Singleton),每次都实例化一个新的。

public enum Singleton {
    final Delegate delegate = new Delegate();

    public String getValue(String key) {
        return delegate.getValue(key);
    }

    // etc
}

class Delegate { // probably best as package-private
    private final Map<String, String> _mapper = new HashMap<String, String>();

    public String getValue(String key) {
        return _mapper.get(key);
    }

    // etc
}

这为您提供了一个非全局状态的Delegate类,更容易测试。 Singleton类中的全局状态是它在枚举的类加载时创建的全局Delegate实例。

当然,测试任何使用Singleton的类都会比较棘手,但这似乎不是你的问题。

答案 2 :(得分:0)

这是你可能想要考虑的事情。

  enum Singleton {
    INSTANCE;

    ThreadLocal<Map<String, String>> context = new ThreadLocal<Map<String, String>>();
    private final Map<String, String> _mapper = new HashMap<String, String>();

    void createContext() {
      context.set(new HashMap<String, String>());
    }

    Map<String, String> getMapper() {
      Map<String, String> mapper;
      mapper = context.get();
      if (mapper==null) {
        mapper = _mapper;
      }
      return mapper;
    }

    public String getValue(String key) {
      return getMapper().get(key);
    }
    // ...
  }

在测试中,您可以在设置过程中致电INSTANCE.createContext()

如果从未调用createContext,则不会创建线程局部变量,因此使用正常的_mapper。否则,将检索并使用线程局部变量。