在 Java 中模拟静态块
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/61150/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mocking Static Blocks in Java
提问by Cem Catikkas
My motto for Java is "just because Java has static blocks, it doesn't mean that you should be using them." Jokes aside, there are a lot of tricks in Java that make testing a nightmare. Two of the most I hate are Anonymous Classes and Static Blocks. We have a lot of legacy code that make use of Static Blocks and these are one of the annoying points in our push in writing unit tests. Our goal is to be able to write unit tests for classes that depend on this static initialization with minimal code changes.
我对 Java 的座右铭是“仅仅因为 Java 具有静态块,并不意味着您应该使用它们。” 撇开笑话不谈,Java 中有很多技巧让测试变成了一场噩梦。我最讨厌的两个是匿名类和静态块。我们有很多使用静态块的遗留代码,这些是我们推动编写单元测试的恼人点之一。我们的目标是能够以最少的代码更改为依赖于这种静态初始化的类编写单元测试。
So far my suggestion to my colleagues is to move the body of the static block into a private static method and call it staticInit
. This method can then be called from within the static block. For unit testing another class that depends on this class could easily mock staticInit
with JMockitto not do anything. Let's see this in example.
到目前为止,我对同事的建议是将静态块的主体移动到私有静态方法中并调用它staticInit
。然后可以从静态块中调用此方法。对于单元测试,依赖于这个类的另一个类可以很容易地staticInit
用JMockit模拟而不做任何事情。让我们看看这个例子。
public class ClassWithStaticInit {
static {
System.out.println("static initializer.");
}
}
Will be changed to
将改为
public class ClassWithStaticInit {
static {
staticInit();
}
private static void staticInit() {
System.out.println("static initialized.");
}
}
So that we can do the following in a JUnit.
这样我们就可以在JUnit 中执行以下操作。
public class DependentClassTest {
public static class MockClassWithStaticInit {
public static void staticInit() {
}
}
@BeforeClass
public static void setUpBeforeClass() {
Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
}
}
However this solution also comes with its own problems. You can't run DependentClassTest
and ClassWithStaticInitTest
on the same JVM since you actually want the static block to run for ClassWithStaticInitTest
.
然而,这个解决方案也有其自身的问题。您不能在同一个 JVM 上运行DependentClassTest
和ClassWithStaticInitTest
,因为您实际上希望静态块为ClassWithStaticInitTest
.
What would be your way of accomplishing this task? Or any better, non-JMockit based solutions that you think would work cleaner?
你完成这项任务的方法是什么?或者任何更好的、基于非 JMockit 的解决方案,您认为它们会更干净?
采纳答案by Mike Stone
When I run into this problem, I usually do the same thing you describe, except I make the static method protected so I can invoke it manually. On top of this, I make sure that the method can be invoked multiple times without problems (otherwise it is no better than the static initializer as far as the tests go).
当我遇到这个问题时,我通常会做你描述的同样的事情,除了我将静态方法设置为 protected 以便我可以手动调用它。最重要的是,我确保可以多次调用该方法而不会出现问题(否则就测试而言,它并不比静态初始化程序好)。
This works reasonably well, and I can actually test that the static initializer method does what I expect/want it to do. Sometimes it is just easiest to have some static initialization code, and it just isn't worth it to build an overly complex system to replace it.
这工作得相当好,我实际上可以测试静态初始化方法是否符合我的期望/希望它做的事情。有时,拥有一些静态初始化代码是最简单的,而构建一个过于复杂的系统来替换它是不值得的。
When I use this mechanism, I make sure to document that the protected method is only exposed for testing purposes, with the hopes that it won't be used by other developers. This of course may not be a viable solution, for example if the class' interface is externally visible (either as a sub-component of some kind for other teams, or as a public framework). It is a simple solution to the problem though, and doesn't require a third party library to set up (which I like).
当我使用这种机制时,我确保记录受保护的方法仅用于测试目的,希望它不会被其他开发人员使用。这当然可能不是一个可行的解决方案,例如,如果类的接口是外部可见的(作为其他团队的某种子组件,或者作为公共框架)。不过,这是该问题的一个简单解决方案,并且不需要设置第三方库(我喜欢)。
回答by Justin Standard
Sounds to me like you are treating a symptom: poor design with dependencies on static initialization. Maybe some refactoring is the real solution. It sounds like you've already done a little refactoring with your staticInit()
function, but maybe that function needs to be called from the constructor, not from a static initializer. If you can do away with static initializers period, you will be better off. Only you can make this decision (I can't see your codebase) but some refactoring will definitely help.
在我看来,您正在处理一个症状:糟糕的设计依赖于静态初始化。也许一些重构是真正的解决方案。听起来您已经对您的staticInit()
函数进行了一些重构,但也许该函数需要从构造函数中调用,而不是从静态初始化程序中调用。如果您可以取消静态初始化程序期,您会过得更好。只有您可以做出这个决定(我看不到您的代码库),但进行一些重构肯定会有所帮助。
As for mocking, I use EasyMock, but I have run into the same issue. Side effects of static initializers in legacy code make testing difficult. Our answer was to refactor out the static initializer.
至于模拟,我使用 EasyMock,但我遇到了同样的问题。遗留代码中静态初始值设定项的副作用使测试变得困难。我们的答案是重构静态初始值设定项。
回答by Henrik Gustafsson
I suppose you really want some kind of factory instead of the static initializer.
我想你真的想要某种工厂而不是静态初始值设定项。
Some mix of a singleton and an abstract factory would probably be able to get you the same functionality as today, and with good testability, but that would add quite a lot of boiler-plate code, so it might be better to just try to refactor the static stuff away completely or if you could at least get away with some less complex solution.
单例和抽象工厂的某种混合可能能让您获得与今天相同的功能,并且具有良好的可测试性,但这会增加大量样板代码,因此最好尝试重构静态的东西完全消失,或者如果你至少可以摆脱一些不太复杂的解决方案。
Hard to tell if it′s possible without seeing your code though.
但是,如果没有看到您的代码,很难判断是否可行。
回答by marcospereira
You could write your test code in Groovy and easily mock the static method using metaprogramming.
您可以在 Groovy 中编写测试代码,并使用元编程轻松模拟静态方法。
Math.metaClass.'static'.max = { int a, int b ->
a + b
}
Math.max 1, 2
If you can't use Groovy, you will really need to refactoring the code (maybe to inject something like a initializator).
如果您不能使用 Groovy,您将真的需要重构代码(也许注入类似初始化程序的东西)。
Kind Regards
亲切的问候
回答by martinatime
I'm not super knowledgeable in Mock frameworks so please correct me if I'm wrong but couldn't you possibly have two different Mock objects to cover the situations that you mention? Such as
我对 Mock 框架不是很了解,所以如果我错了,请纠正我,但你不能有两个不同的 Mock 对象来涵盖你提到的情况吗?如
public static class MockClassWithEmptyStaticInit {
public static void staticInit() {
}
}
and
和
public static class MockClassWithStaticInit {
public static void staticInit() {
System.out.println("static initialized.");
}
}
Then you can use them in your different test cases
然后你可以在不同的测试用例中使用它们
@BeforeClass
public static void setUpBeforeClass() {
Mockit.redefineMethods(ClassWithStaticInit.class,
MockClassWithEmptyStaticInit.class);
}
and
和
@BeforeClass
public static void setUpBeforeClass() {
Mockit.redefineMethods(ClassWithStaticInit.class,
MockClassWithStaticInit.class);
}
respectively.
分别。
回答by Cem Catikkas
This is going to get into more "Advanced" JMockit. It turns out, you can redefine static initialization blocks in JMockit by creating a public void $clinit()
method. So, instead of making this change
这将进入更“高级”的 JMockit。事实证明,您可以通过创建public void $clinit()
方法在 JMockit 中重新定义静态初始化块。所以,而不是做这个改变
public class ClassWithStaticInit {
static {
staticInit();
}
private static void staticInit() {
System.out.println("static initialized.");
}
}
we might as well leave ClassWithStaticInit
as is and do the following in the MockClassWithStaticInit
:
我们不妨保持原样ClassWithStaticInit
,并在以下内容中执行以下操作MockClassWithStaticInit
:
public static class MockClassWithStaticInit {
public void $clinit() {
}
}
This will in fact allow us to not make any changes in the existing classes.
这实际上将允许我们不对现有类进行任何更改。
回答by Jan Kronquist
PowerMockis another mock framework that extends EasyMock and Mockito. With PowerMock you can easily remove unwanted behaviorfrom a class, for example a static initializer. In your example you simply add the following annotations to your JUnit test case:
PowerMock是另一个扩展 EasyMock 和 Mockito 的模拟框架。使用 PowerMock,您可以轻松地从类中删除不需要的行为,例如静态初始化程序。在您的示例中,您只需将以下注释添加到 JUnit 测试用例中:
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")
PowerMock does not use a Java agent and therefore does not require modification of the JVM startup parameters. You simple add the jar file and the above annotations.
PowerMock 不使用 Java 代理,因此不需要修改 JVM 启动参数。您只需添加 jar 文件和上述注释。
回答by matsev
Occasionally, I find static initilizers in classes that my code depends on. If I cannot refactor the code, I use PowerMock's @SuppressStaticInitializationFor
annotation to suppress the static initializer:
有时,我会在我的代码所依赖的类中找到静态初始化程序。如果我无法重构代码,我会使用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...
}
Read more about suppressing unwanted behaviour.
阅读有关抑制不需要的行为的更多信息。
Disclaimer: PowerMock is an open source project developed by two colleagues of mine.
免责声明:PowerMock 是我的两个同事开发的开源项目。
回答by KidCrippler
Not really an answer, but just wondering - isn't there any way to "reverse" the call to Mockit.redefineMethods
?
If no such explicit method exists, shouldn't executing it again in the following fashion do the trick?
不是真正的答案,只是想知道 - 有没有办法“逆转”呼叫Mockit.redefineMethods
?
如果不存在这样的显式方法,不应该以以下方式再次执行它吗?
Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class);
If such a method exists, you could execute it in the class' @AfterClass
method, and test ClassWithStaticInitTest
with the "original" static initializer block, as if nothing has changed, from the same JVM.
如果存在这样的方法,您可以在类的@AfterClass
方法中执行它,并ClassWithStaticInitTest
使用“原始”静态初始化程序块进行测试,就好像什么都没有改变一样,来自同一个 JVM。
This is just a hunch though, so I may be missing something.
不过这只是一种预感,所以我可能会遗漏一些东西。