【问题标题】:PowerMock: mock out private static final variable, a concrete examplePowerMock:mock out private static final 变量,一个具体的例子
【发布时间】:2014-06-03 11:01:00
【问题描述】:

要通过此测试,必须做的绝对最少的模拟是多少?

代码:

class PrivateStaticFinal {
    private static final Integer variable = 0;
    public static Integer method() { return variable + 1; }
}

测试:

@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivateStaticFinal.class)
class PrivateStaticFinalTest {
    @Test
    public void testMethod() {
        //TODO PrivateStaticFinal.variable = 100
        assertEquals(PrivateStaticFinal.method(), 101);
    }
}

相关:Mock private static final variables in the testing class(没有明确答案)

【问题讨论】:

  • 这些静态变量都在同一个类中,这很棘手。我遇到的所有问题都是在尝试测试课程时使用来自其他地方的静态数据。不得不模拟您尝试测试的课程的部分部分似乎很糟糕。你永远不应该嘲笑你正在测试的课程,只是它所依赖的东西。

标签: powermock


【解决方案1】:

免责声明:经过大量寻找各种线程后,我找到了答案。可以做到,但普遍的共识是它不是很安全,但仅在单元测试中看到你是如何做到这一点的,我认为你接受这些风险:)


答案不是 Mocking,因为大多数 Mocking 不允许你闯入决赛。答案有点“hacky”,当 Java 调用核心 java.lang.reflect.Fieldjava.lang.reflect.Modifier 类(反射)时,您实际上是在修改私有字段。看着this answer,我能够拼凑出你剩下的测试,而不需要模拟来解决你的问题。

这个答案的问题是我在尝试修改variable 时遇到了NoSuchFieldException。这方面的帮助在于 another post 关于如何访问私有而不是公共的字段。

反射/场操作说明:

由于 Mocking 无法处理 final,因此我们最终要做的是侵入字段本身的根。当我们使用Field 操作(反射)时,我们正在寻找类/对象内部的特定变量。一旦 Java 找到它,我们就会得到它的“修饰符”,它告诉变量它有哪些限制/规则,例如 finalstaticprivatepublic 等。我们找到正确的变量,然后告诉代码它是可访问的,这允许我们更改这些修饰符。一旦我们更改了根的“访问”以允许我们对其进行操作,我们将关闭它的“最终”部分。然后我们可以更改该值并将其设置为我们需要的任何值。

简单地说,我们正在修改变量以允许我们更改其属性,删除final 的属性,然后更改值,因为它不再是final。欲了解更多信息,check out the post where the idea came from

所以我们一步一步传入我们想要操作的变量,然后...

// Mark the field as public so we can toy with it
field.setAccessible(true);
// Get the Modifiers for the Fields
Field modifiersField = Field.class.getDeclaredField("modifiers");  
// Allow us to change the modifiers
modifiersField.setAccessible(true);
 // Remove final modifier from field by blanking out the bit that says "FINAL" in the Modifiers
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// Set new value
field.set(null, newValue); 

将这一切组合成一个新的超级答案。

@RunWith(PowerMockRunner.class)
@PrepareForTest()
class PrivateStaticFinalTest {
    @Test
    public void testMethod(){
      try {
        setFinalStatic(PrivateStaticFinal.class.getDeclaredField("variable"), Integer.valueOf(100));
      } 
      catch (SecurityException e) {fail();}
      catch (NoSuchFieldException e) {fail();}
      catch (Exception e) {fail();}
      assertEquals(PrivateStaticFinal.method(), Integer.valueOf(101));
    }

    static void setFinalStatic(Field field, Object newValue) throws Exception {
        field.setAccessible(true);
        // remove final modifier from field
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, newValue);
    }
}

更新 上述解决方案仅适用于在静态块中初始化的常量。当同时声明和初始化常量时,可能会发生编译器内联它,此时对原始值的任何更改都会被忽略。

【讨论】:

  • 你能解释一下什么 ` Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); ` 有吗?
  • 编辑了答案,做了更多的解释,以及指向原始文章的链接,这是“修改”“修饰符”以允许我们破解的想法的来源变量本身并删除它的最终确定性。
  • 谢谢!我希望测试代码仍然尊重源代码,所以在我们设置了我们想要的值之后modifiersField.setInt(field, field.getModifiers() & Modifier.FINAL); modifiersField.setAccessible(false); 会起作用。即“做/撤消”的事情。
  • 您基本上是在运行时看到变量,并且允许通过反射对其进行修改。改回来应该仍然有效(我测试时确实如此),但我认为那时你已经走得太远了。源没有改变,运行时看到的东西正在改变。它非常混乱,但似乎是修改static final而不在源代码中更改它以反映测试数据的唯一方法。如需更多信息,请查看this answer
  • 注意:只有在静态块中创建常量时才能可靠地工作。在同时声明和初始化常量时,编译器可能会内联它,此时对原始值的任何更改都会被忽略。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-28
  • 1970-01-01
  • 2011-07-02
  • 2022-08-22
  • 2012-01-23
  • 2015-05-11
相关资源
最近更新 更多