简体   繁体   English

Mocking Static Java 中的块

[英]Mocking Static Blocks in Java

My motto for Java is "just because Java has static blocks, it doesn't mean that you should be using them."我对 Java 的座右铭是“仅仅因为 Java 有 static 块,这并不意味着你应该使用它们。” Jokes aside, there are a lot of tricks in Java that make testing a nightmare.撇开玩笑不谈,Java 中有很多技巧让测试成为一场噩梦。 Two of the most I hate are Anonymous Classes and Static Blocks.我最讨厌的两个是匿名类和 Static 块。 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.我们有很多使用 Static 块的遗留代码,这些是我们推动编写单元测试的恼人点之一。 Our goal is to be able to write unit tests for classes that depend on this static initialization with minimal code changes.我们的目标是能够以最少的代码更改为依赖于此 static 初始化的类编写单元测试。

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 .到目前为止,我对同事的建议是将 static 块的主体移动到私有 static 方法中,并将其命名为staticInit This method can then be called from within the static block.然后可以从 static 块中调用此方法。 For unit testing another class that depends on this class could easily mock staticInit with JMockit to not do anything.对于依赖此 class 的另一个 class 的单元测试,可以轻松地使用JMockit模拟staticInit以不做任何事情。 Let's see this in example.让我们看看这个例子。

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 上运行DependentClassTestClassWithStaticInitTest ,因为您实际上希望 static 块为ClassWithStaticInitTest运行。

What would be your way of accomplishing this task?完成这项任务的方法是什么? Or any better, non-JMockit based solutions that you think would work cleaner?或者任何更好的、非基于 JMockit 的解决方案,您认为它们会更干净?

PowerMock is another mock framework that extends EasyMock and Mockito. PowerMock是另一个扩展 EasyMock 和 Mockito 的模拟框架。 With PowerMock you can easily remove unwanted behavior from a class, for example a static initializer.使用 PowerMock,您可以轻松地从 class 中删除不需要的行为,例如 static 初始化程序。 In your example you simply add the following annotations to your JUnit test case:在您的示例中,您只需将以下注释添加到您的 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. PowerMock 不使用 Java 代理,因此不需要修改 JVM 启动参数。 You simple add the jar file and the above annotations.您只需添加 jar 文件和上述注释即可。

Occasionally, I find static initilizers in classes that my code depends on.有时,我会在我的代码所依赖的类中找到 static 初始化程序。 If I cannot refactor the code, I use PowerMock 's @SuppressStaticInitializationFor annotation to suppress the static initializer:如果我无法重构代码,我使用PowerMock@SuppressStaticInitializationFor注释来抑制 static 初始化程序:

@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 是我的两个同事开发的开源项目。

This is going to get into more "Advanced" JMockit.这将进入更“高级”的 JMockit。 It turns out, you can redefine static initialization blocks in JMockit by creating a public void $clinit() method.事实证明,您可以通过创建public void $clinit()方法在 JMockit 中重新定义 static 初始化块。 So, instead of making this change所以,而不是做这个改变

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.这实际上将允许我们不对现有类进行任何更改。

Sounds to me like you are treating a symptom: poor design with dependencies on static initialization.在我看来,您正在治疗一种症状:依赖于 static 初始化的糟糕设计。 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.听起来您已经对您的staticInit() function 进行了一些重构,但可能需要从构造函数调用 function,而不是从 ZA81259CEF8E959C3224DF1D456E 初始化程序调用。 If you can do away with static initializers period, you will be better off.如果您可以取消 static 初始化程序期间,您会过得更好。 Only you can make this decision ( I can't see your codebase ) but some refactoring will definitely help.只有你可以做出这个决定(我看不到你的代码库),但一些重构肯定会有所帮助。

As for mocking, I use EasyMock, but I have run into the same issue.至于mocking,我用的是EasyMock,但我也遇到了同样的问题。 Side effects of static initializers in legacy code make testing difficult.遗留代码中 static 初始化程序的副作用使测试变得困难。 Our answer was to refactor out the static initializer.我们的答案是重构 static 初始化程序。

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.当我遇到这个问题时,我通常会做你描述的同样的事情,除了我将 static 方法保护起来,以便我可以手动调用它。 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).最重要的是,我确保可以多次调用该方法而不会出现问题(否则就测试而言,它并不比 static 初始化程序好)。

This works reasonably well, and I can actually test that the static initializer method does what I expect/want it to do.这工作得相当好,我实际上可以测试 static 初始化方法是否符合我的期望/希望它做的事情。 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.有时,拥有一些 static 初始化代码是最简单的,而构建一个过于复杂的系统来替换它是不值得的。

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).不过,这是解决问题的简单方法,并且不需要设置第三方库(我喜欢)。

You could write your test code in Groovy and easily mock the static method using metaprogramming.您可以在 Groovy 中编写测试代码,并使用元编程轻松模拟 static 方法。

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亲切的问候

I suppose you really want some kind of factory instead of the static initializer.我想你真的想要某种工厂而不是 static 初始化程序。

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. singleton 和抽象工厂的一些组合可能能够为您提供与今天相同的功能,并且具有良好的可测试性,但这会添加相当多的样板代码,因此尝试重构可能会更好static 完全消失,或者如果您至少可以使用一些不太复杂的解决方案。

Hard to tell if it´s possible without seeing your code though.不过,如果没有看到您的代码,很难判断它是否可能。

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?我对 Mock 框架不是很了解,所以如果我错了请纠正我,但你不能有两个不同的 Mock 对象来涵盖你提到的情况吗? Such as

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.分别。

Not really an answer, but just wondering - isn't there any way to "reverse" the call to Mockit.redefineMethods ?不是真正的答案,只是想知道 - 有没有办法“逆转”对Mockit.redefineMethods的调用?
If no such explicit method exists, shouldn't executing it again in the following fashion do the trick?如果不存在这样的显式方法,不应该以下列方式再次执行它吗?

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方法中执行它,并使用“原始”static 初始化程序块测试ClassWithStaticInitTest ,就好像没有任何变化一样,从同一个 JVM。

This is just a hunch though, so I may be missing something.不过,这只是一种预感,所以我可能会遗漏一些东西。

You can use PowerMock to execute the private method call like:您可以使用 PowerMock 执行私有方法调用,例如:

ClassWithStaticInit staticInitClass = new ClassWithStaticInit()
Whitebox.invokeMethod(staticInitClass, "staticInit");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM