【问题标题】:Unit testing static method which uses a resource bundle使用资源包的单元测试静态方法
【发布时间】:2025-12-13 21:45:01
【问题描述】:

我已经阅读了很多关于使用 Powermock 和 Mockito 的文章并尝试了很多不同的方法,但我仍然无法弄清楚如何对下面的静态方法进行单元测试。

public static Map<String, String> getEntries() {
    Map<String, String> myEntriesMap = new TreeMap<String, String>();
    ResourceBundle myEntries = ResourceBundle.getBundle(ENTRIES_BUNDLE);
    Enumeration<String> enumList = myEntries.getKeys();
    String key = null;
    String value = null;
    while (enumList.hasMoreElements()) {
        key = enumList.nextElement().toString();
        value = myEntries.getString(key);
        myEntriesMap.put(key, value);
    }
    return myEntriesMap;
}

代码是包含大约 30 个这样的静态方法的(遗留)类的一部分,重构并不是一个真正的选择。类似地,在其他一些静态方法中,正在检索 DBconnections。

例如:我如何模拟资源包 ENTRIES_BUNDLE 并对这个方法进行单元测试? 我正在寻找一种可以普遍应用于所有静态方法的模式。

【问题讨论】:

  • 是的,这种方法在目前的形式下并不是真正的单元测试。不是因为它是静态的,而是因为调用了ResourceBundle.getBundle。理论上,如果你真的需要的话,你可以使用 PowerMock 来完成,尽管重构会是一个更好的选择。但我看了你的问题,我想知道你为什么要对遗留类进行单元测试。当然,如果它是一个遗留类,那么从单元测试中受益的大部分机会已经过去了?如果不允许您更改它,那么如果您的测试发现了错误,您会怎么做?
  • 感谢您的回答。我完全同意你现在写 Junits 所产生的价值。问题是我们有这个项目,部分代码被 JUnits 覆盖,而部分代码没有。这个想法是用 Junits 更新整个代码,并用单元测试用例覆盖新的开发。代码是遗留的并且经过测试并且稳定,是我们不想重构它的原因(至少在这个时间点上)。但是话虽如此,看看你的评论,如果你能指出我如何使用 Powermock 来做到这一点,那将非常有帮助。
  • 我要为此直接模拟地狱 - 但code.google.com/p/powermock/wiki/MockitoUsage13 有关于如何模拟静态方法的信息。从顶部向下大约一屏。但老实说,如果可以的话,先试试罗杰里奥的方法。
  • 在发布问题之前,我已经尝试过 Rogério 的方法。但是我正在寻找一种可以用于其他条件以及数据库连接等的模式。但是根据您的建议/想法,我正在认真考虑重构一些原始代码。让我们看看管理层对此有何评论。但毕竟我可能会在模拟地狱中见到你 ;-) 感谢您的时间和帮助!

标签: java unit-testing junit4 mockito powermock


【解决方案1】:

使用 ResourceBundle.getBundle(String, ResourceBundle.Control) 让 ResourceBundle 缓存给定字符串的包。你可以继承 ResourceBundle.Control 来提供你想要的任何类型的包。

@Test
public void myTest()
{
    // In your Test's init phase run an initial "getBundle()" call
    // with your control.  This will cause ResourceBundle to cache the result.
    ResourceBundle rb1 = ResourceBundle.getBundle( "blah", myControl );

    // And now calls without the supplied Control will still return
    // your mocked bundle.  Yay!
    ResourceBundle rb2 = ResourceBundle.getBundle( "blah" );
}

这是子类控件:

ResourceBundle.Control myControl = new ResourceBundle.Control()
{
    public ResourceBundle newBundle( String baseName, Locale locale, String format,
            ClassLoader loader, boolean reload )
    {
        return myBundle;
    }
};

这是模拟 ResourceBundle 的一种方法(使用单元测试所需的键/值填充 TreeMap,留给读者作为练习):

ResourceBundle myBundle = new ResourceBundle()
{
    protected void setParent( ResourceBundle parent )
    {
      // overwritten to do nothing, otherwise ResourceBundle.getBundle(String)
      //  gets into an infinite loop!
    }

    TreeMap<String, String> tm = new TreeMap<String, String>();

    @Override
    protected Object handleGetObject( String key )
    {
        return tm.get( key );
    }

    @Override
    public Enumeration<String> getKeys()
    {
        return Collections.enumeration( tm.keySet() );
    }
};

【讨论】:

    【解决方案2】:

    您不需要模拟ResourceBundle.getBundle 方法。只需在测试源代码树的适当位置创建一个“.properties”文件即可。这仍然是一个非常好的和有用的单元测试。

    【讨论】:

    • +1 这很好很干净。我看到的唯一问题是测试是否需要与应用程序代码不同的属性文件,或者不同的测试是否需要彼此不同的属性文件。如果这被证明是一个问题,您将需要编写设置 Properties 对象的测试,然后将它们写入“实时”空间。
    【解决方案3】:

    我们在模拟 ResourceBundle.getString() 方法时遇到了类似的问题。

    java.util.MissingResourceException: Can't find resource for bundle $java.util.ResourceBundle$$EnhancerByMockitoWithCGLIB$$e9ea44f0, key name

    我们的问题是该方法是最终的,这使得 mockito 无法模拟该方法。

    相反,我们使用了这个灵魂:https://code.google.com/p/powermock/wiki/MockSystem

    请注意,@PrepareForTest({ClassThatCallsTheSystemClass.class}) 不是 ResourceBundle 类!

    【讨论】:

      【解决方案4】:

      如果您使用以下库:mockito-all 和 jmockit,请执行以下步骤:

      假设你想从 xxxx.class 模拟方法 yyyy

      @MockClass(realClass = xxxx.class)
      public static class MyClass {
           @Mock
           public static void yyyy(){
                ......
           }
      }
      

      在你的测试中:

      @Test
      public void test() {
           Mockit.setUpMock(MyClass.class);
      }
      

      【讨论】:

      • 要让这个答案很好,您应该真正描述您正在使用的模拟框架。 OP 询问了 Mockito 和 PowerMock - 这两者都不是。我们必须在构建中添加什么才能使这个测试正常工作?
      • 我正在使用 mockito-all 和 jmockit 库