在Java中嘲笑静态块

我对Java的座右铭是“仅仅因为Java有静态块,并不意味着你应该使用它们”。 除了笑话之外,Java中有很多技巧使得testing成为一场噩梦。 我最讨厌的两个是匿名类和静态块。 我们有很多使用静态块的遗留代码,这些都是我们在编写unit testing时的烦人之处。 我们的目标是能够编写unit testing的类,依赖于这个静态初始化与最小的代码更改。

到目前为止,我的同事的build议是将静态块的主体移动到一个私有的静态方法,并将其staticInit 。 这个方法可以在静态块中被调用。 对于unit testing,依赖于这个类的另一个类可以很容易地使用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); } } 

但是这个解决scheme也带来了自己的问题。 您不能在同一JVM上运行DependentClassTestClassWithStaticInitTest ,因为您实际上需要为ClassWithStaticInitTest运行静态块。

你将如何完成这个任务? 或者更好的,非JMockit的解决scheme,你认为会更清洁?

当我碰到这个问题的时候,我通常会和你描述的一样,除了让静态方法受到保护,所以我可以手动调用它。 最重要的是,我确信这个方法可以被多次调用而没有任何问题(否则就不会比静态初始化方法更好)。

这工作相当好,我实际上可以testing静态初始化方法做我希望/希望它做。 有时候,最简单的办法是有一些静态的初始化代码,只是不值得构build一个过于复杂的系统来代替它。

当我使用这种机制时,我确保logging受保护的方法仅用于testing目的,希望它不会被其他开发者使用。 这当然可能不是一个可行的解决scheme,例如,如果类的接口是外部可见的(作为其他团队的某种子组件或作为公共框架)。 这是一个简单的解决scheme,虽然,并不需要第三方库设置(我喜欢)。

PowerMock是扩展EasyMock和Mockito的另一个模拟框架。 使用PowerMock,您可以轻松地从类中移除不需要的行为 ,例如静态初始化程序。 在您的示例中,您只需将以下注释添加到JUnittesting用例中即可:

 @RunWith(PowerMockRunner.class) @SuppressStaticInitializationFor("some.package.ClassWithStaticInit") 

PowerMock不使用Java代理,因此不需要修改JVM启动参数。 你简单的添加jar文件和上面的注释。

这将进入更多的“高级”JMockit。 事实certificate,您可以通过创build一个public void $clinit()方法来重新定义JMockit中的静态初始化块。 所以,而不是做这个改变

 public class ClassWithStaticInit { static { staticInit(); } private static void staticInit() { System.out.println("static initialized."); } } 

我们不妨按照原样离开ClassWithStaticInit ,并在MockClassWithStaticInit执行以下MockClassWithStaticInit

 public static class MockClassWithStaticInit { public void $clinit() { } } 

这实际上可以让我们不对现有的课程做任何改变。

偶尔,我发现我的代码依赖的类中的静态initilizers。 如果我不能重构代码,我使用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... } 

详细了解抑制不需要的行为 。

免责声明:PowerMock是由我的两位同事开发的开源项目。

听起来像你正在治疗一个症状:糟糕的devise依赖于静态初始化。 也许一些重构是真正的解决scheme。 这听起来像你已经做了一些你的staticInit()函数重构,但也许该函数需要从构造函数调用,而不是从静态初始化。 如果你可以消除静态初始化器的时间,你会变得更好。 只有你可以做出这个决定( 我看不到你的代码库 ),但是一些重构肯定会有帮助。

至于嘲笑,我使用EasyMock,但我遇到了同样的问题。 遗留代码中静态初始化器的副作用使testing变得困难。 我们的答案是重构静态初始化器。

您可以在Groovy中编写testing代码,并使用元编程轻松地模拟静态方法。

 Math.metaClass.'static'.max = { int a, int b -> a + b } Math.max 1, 2 

如果你不能使用Groovy,你将需要重构代码(也许注入一些像初始化的东西)。

亲切的问候

我想你真的想要某种工厂而不是静态初始化器。

单身人士和抽象工厂的混合可能会得到与今天相同的function,并具有良好的可testing性,但是这样会增加很多的锅炉代码,所以尝试重构可能会更好静态的东西完全消失,或者如果你至less可以摆脱一些不那么复杂的解决scheme。

很难说如果没有看到你的代码是可能的。

我对Mock框架不是很了解,所以如果我错了,请纠正我的错误,但是你不可能有两个不同的Mock对象来覆盖你提到的情况吗? 如

 public static class MockClassWithEmptyStaticInit { public static void staticInit() { } } 

 public static class MockClassWithStaticInit { public static void staticInit() { System.out.println("static initialized."); } } 

然后你可以在不同的testing用例中使用它们

 @BeforeClass public static void setUpBeforeClass() { Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithEmptyStaticInit.class); } 

 @BeforeClass public static void setUpBeforeClass() { Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class); } 

分别。

不是一个真正的答案,但只是想知道 – 是不是有任何方法来“调用” Mockit.redefineMethods调用?
如果不存在这样的显式方法,不应该以下面的方式再次执行这个方法吗?

 Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class); 

如果存在这样的方法,您可以在类的@AfterClass方法中执行它,并使用“原始”静态初始化程序块testingClassWithStaticInitTest ,就像从同一个JVM中没有任何更改一样。

这只是一个预感,所以我可能会错过一些东西。