我对Java的座右铭是"仅仅因为Java有静态块,它并不意味着你应该使用它们." 除了笑话之外,Java中有很多技巧会让测试成为一场噩梦.我最讨厌的两个是匿名类和静态块.我们有很多使用静态块的遗留代码,这些是我们编写单元测试的烦恼之一.我们的目标是能够为依赖于此静态初始化的类编写单元测试,并且代码更改最少.
到目前为止,我对同事的建议是将静态块的主体移动到私有静态方法中并调用它staticInit
.然后可以从静态块内调用此方法.对于单元测试,依赖于此类的另一个类可以轻松地staticInit
使用JMockit进行模拟而不执行任何操作.我们在示例中看到这一点.
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的解决方案更干净?
PowerMock是另一个扩展EasyMock和Mockito的模拟框架.使用PowerMock,您可以轻松地从类中删除不需要的行为,例如静态初始化程序.在您的示例中,您只需将以下注释添加到JUnit测试用例:
@RunWith(PowerMockRunner.class) @SuppressStaticInitializationFor("some.package.ClassWithStaticInit")
PowerMock不使用Java代理,因此不需要修改JVM启动参数.您可以简单地添加jar文件和上面的注释.
这将进入更多"高级"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() { } }
这实际上允许我们不对现有类进行任何更改.
偶尔,我会在我的代码所依赖的类中找到静态初始化器.如果我不能重构代码,我使用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是由我的两位同事开发的开源项目.
听起来像你正在治疗一个症状:设计不良,依赖于静态初始化.也许一些重构是真正的解决方案.听起来你已经对你的staticInit()
函数进行了一些重构,但是可能需要从构造函数调用该函数,而不是从静态初始化器调用.如果你可以取消静态初始化期间,你会更好.只有你可以做出这个决定(我看不到你的代码库)但是一些重构肯定会有所帮助.
至于模拟,我使用EasyMock,但我遇到了同样的问题.遗留代码中静态初始化器的副作用使测试变得困难.我们的答案是重构静态初始化器.
当我遇到这个问题时,我通常做你描述的同样的事情,除了我保护静态方法所以我可以手动调用它.最重要的是,我确保可以多次调用该方法而不会出现问题(否则,就测试而言,它并不比静态初始化器好).
这工作得相当好,我实际上可以测试静态初始化方法是否符合我的期望/希望它做什么.有时最简单的方法就是拥有一些静态初始化代码,而构建一个过于复杂的系统来替换它是不值得的.
当我使用这种机制时,我确保记录受保护的方法仅出于测试目的而暴露,并希望其他开发人员不会使用它.这当然可能不是一个可行的解决方案,例如,如果类的接口是外部可见的(作为其他团队的某种子组件,或作为公共框架).这是一个简单的问题解决方案,并且不需要第三方库来设置(我喜欢).