我对Java的座右铭是“仅仅因为Java有静态块,它并不意味着你应该使用它们。”除了笑话之外,Java中有很多技巧会让测试成为一场噩梦。我最讨厌的两个是匿名类和静态块。我们有很多使用静态块的遗留代码,这些是我们编写单元测试的烦恼之一。我们的目标是能够为依赖于此静态初始化的类编写单元测试,并且代码更改最少。
到目前为止,我向同事提出的建议是将静态块的主体移动到私有静态方法中并将其称为staticInit
。然后可以从静态块内调用此方法。对于单元测试,依赖于此类的另一个类可以轻松地使用JMockit模拟staticInit
以不执行任何操作。我们在示例中看到这一点。
public class ClassWithStaticInit {
static {
System.out.println("static initializer.");
}
}
将更改为
public class ClassWithStaticInit {
static {
staticInit();
}
private static void staticInit() {
System.out.println("static initialized.");
}
}
这样我们就可以在JUnit中执行以下操作。
public class DependentClassTest {
public static class MockClassWithStaticInit {
public static void staticInit() {
}
}
@BeforeClass
public static void setUpBeforeClass() {
Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
}
}
然而,这个解决方案也有自己的问题。您无法在同一JVM上运行DependentClassTest
和ClassWithStaticInitTest
,因为您实际上希望为ClassWithStaticInitTest
运行静态块。
您完成此任务的方式是什么?或者你认为哪种更好的,基于非JMockit的解决方案更干净?
答案 0 :(得分:41)
PowerMock是另一个扩展EasyMock和Mockito的模拟框架。使用PowerMock,您可以从类中轻松remove unwanted behavior,例如静态初始化程序。在您的示例中,您只需将以下注释添加到JUnit测试用例中:
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")
PowerMock不使用Java代理,因此不需要修改JVM启动参数。您可以简单地添加jar文件和上面的注释。
答案 1 :(得分:13)
这将进入更多“高级”JMockit。事实证明,您可以通过创建public void $clinit()
方法在JMockit中重新定义静态初始化块。所以,而不是做这个改变
public class ClassWithStaticInit {
static {
staticInit();
}
private static void staticInit() {
System.out.println("static initialized.");
}
}
我们也可以保留ClassWithStaticInit
,并在MockClassWithStaticInit
中执行以下操作:
public static class MockClassWithStaticInit {
public void $clinit() {
}
}
这实际上允许我们不对现有的类进行任何更改。
答案 2 :(得分:11)
偶尔,我会在我的代码所依赖的类中找到静态初始化器。如果我无法重构代码,我使用PowerMock的@SuppressStaticInitializationFor
注释来禁止静态初始值设定项:
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit")
public class ClassWithStaticInitTest {
ClassWithStaticInit tested;
@Before
public void setUp() {
tested = new ClassWithStaticInit();
}
@Test
public void testSuppressStaticInitializer() {
asserNotNull(tested);
}
// more tests...
}
详细了解suppressing unwanted behaviour。
免责声明:PowerMock是由我的两位同事开发的一个开源项目。
答案 3 :(得分:6)
听起来像是在治疗一种症状:依赖于静态初始化的糟糕设计。也许一些重构是真正的解决方案。听起来你已经使用staticInit()
函数进行了一些重构,但是可能需要从构造函数调用该函数,而不是从静态初始化函数调用。如果你可以取消静态初始化期间,你会更好。只有你可以做出这个决定(我看不到你的代码库)但是一些重构肯定会有所帮助。
至于模拟,我使用EasyMock,但我遇到了同样的问题。遗留代码中静态初始化器的副作用使测试变得困难。我们的答案是重构静态初始化器。
答案 4 :(得分:5)
当我遇到这个问题时,我通常做你描述的同样的事情,除了我保护静态方法所以我可以手动调用它。最重要的是,我确保可以多次调用该方法而没有问题(否则,就测试而言,它并不比静态初始化器好)。
这种方法运行得相当好,我实际上可以测试静态初始化方法是否符合我的期望/希望它做什么。有时候最简单的就是拥有一些静态初始化代码,而构建一个过于复杂的系统来代替它是不值得的。
当我使用这种机制时,我确保记录受保护的方法仅出于测试目的而暴露,希望其他开发人员不会使用它。这当然可能不是一个可行的解决方案,例如,如果类的接口是外部可见的(作为其他团队的某种子组件,或作为公共框架)。这是一个简单的问题解决方案,并且不需要设置第三方库(我喜欢)。
答案 5 :(得分:3)
您可以在Groovy中编写测试代码,并使用元编程轻松模拟静态方法。
Math.metaClass.'static'.max = { int a, int b ->
a + b
}
Math.max 1, 2
如果你不能使用Groovy,你真的需要重构代码(可能注入类似初始化器的东西)。
亲切的问候
答案 6 :(得分:1)
我想你真的想要某种工厂而不是静态初始化器。
单例和抽象工厂的某些组合可能能够为您提供与今天相同的功能,并且具有良好的可测试性,但这会增加相当多的样板代码,因此它可能更好尝试完全重构静态的东西,或者如果你至少可以使用一些不太复杂的解决方案。
虽然没有看到你的代码,但很难说是否可能。
答案 7 :(得分:1)
我对Mock框架并不是超级知识,所以如果我错了请纠正我,但是你不可能有两个不同的Mock对象来覆盖你提到的情况吗?如
public static class MockClassWithEmptyStaticInit {
public static void staticInit() {
}
}
和
public static class MockClassWithStaticInit {
public static void staticInit() {
System.out.println("static initialized.");
}
}
然后您可以在不同的测试用例中使用它们
@BeforeClass
public static void setUpBeforeClass() {
Mockit.redefineMethods(ClassWithStaticInit.class,
MockClassWithEmptyStaticInit.class);
}
和
@BeforeClass
public static void setUpBeforeClass() {
Mockit.redefineMethods(ClassWithStaticInit.class,
MockClassWithStaticInit.class);
}
分别
答案 8 :(得分:0)
不是一个真正的答案,但只是想知道 - 是否有任何方法可以反转"拨打Mockit.redefineMethods
的电话?
如果不存在这样的显式方法,那么不应该以下面的方式再次执行它吗?
Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class);
如果存在这样的方法,你可以在课程中执行它。 @AfterClass
方法,并使用"原始"测试ClassWithStaticInitTest
静态初始化程序块,就好像什么都没有改变一样,来自同一个JVM。
这只是一种预感,所以我可能会遗漏一些东西。
答案 9 :(得分:0)
您可以使用PowerMock执行私有方法调用,例如:
ClassWithStaticInit staticInitClass = new ClassWithStaticInit()
Whitebox.invokeMethod(staticInitClass, "staticInit");