在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上运行DependentClassTest
和ClassWithStaticInitTest
,因为您实际上需要为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中没有任何更改一样。
这只是一个预感,所以我可能会错过一些东西。