代码之家  ›  专栏  ›  技术社区  ›  Cem Catikkas

在Java中模拟静态块

  •  41
  • Cem Catikkas  · 技术社区  · 16 年前

    我对Java的座右铭是“仅仅因为Java有静态块,并不意味着你就应该使用它们。”撇开玩笑不谈,Java中有很多技巧使测试成为一场噩梦。我最讨厌的两个是匿名类和静态块。我们有很多使用静态块的遗留代码,这些是我们编写单元测试时令人恼火的地方之一。我们的目标是能够为依赖于此静态初始化的类编写单元测试,并且代码更改最少。

    到目前为止,我给同事们的建议是将静态块的主体移动到一个私有静态方法中并调用它 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);
      }
    }
    

    DependentClassTest ClassWithStaticInitTest 在同一个JVM上,因为您实际上希望为其运行静态块 ClassWithStaticinitest .

    你完成这项任务的方式是什么?或者是任何更好的、基于非JMockit的解决方案,您认为可以更干净地工作?

    9 回复  |  直到 4 年前
        1
  •  57
  •   Jan Kronquist    15 年前

    PowerMock 是扩展EasyMock和Mockito的另一个模拟框架。使用PowerMock,您可以轻松地 remove unwanted behavior 来自类,例如静态初始值设定项。在您的示例中,只需向JUnit测试用例添加以下注释:

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

    PowerMock不使用Java代理,因此不需要修改JVM启动参数。您只需添加jar文件和上述注释。

        2
  •  14
  •   matsev    13 年前

    有时,我会在我的代码所依赖的类中找到静态initilizer。如果我不能重构代码,我就使用 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...
    }
    

    阅读更多关于 suppressing unwanted behaviour .

        3
  •  13
  •   Cem Catikkas    16 年前

    这将进入更“高级”的JMockit。事实证明,您可以通过创建 public void $clinit() 方法因此,与其做出这种改变

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

    我们还是走吧 ClassWithStaticInit 按原样,在中执行以下操作: MockClassWithStaticInit :

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

        4
  •  6
  •   Justin Standard    16 年前

    在我看来,您正在治疗一种症状:依赖于静态初始化的糟糕设计。也许重构才是真正的解决方案。听起来你已经对你的电脑做了一些重构了 staticInit() 函数,但该函数可能需要从构造函数调用,而不是从静态初始值设定项调用。若你们能在这段时间内去掉静态初始化器,你们会过得更好。只有你才能做出这个决定( 我看不到你的代码库 )但一些重构肯定会有所帮助。

        5
  •  5
  •   Mike Stone    16 年前

    这工作得相当好,我可以实际测试静态初始值设定项方法是否实现了我期望/希望它实现的功能。有时候,拥有一些静态初始化代码是最容易的,而构建一个过于复杂的系统来替换它是不值得的。

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

        6
  •  3
  •   marcospereira    16 年前

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

    如果您不能使用Groovy,那么您将真正需要重构代码(可能需要注入类似于初始化器的东西)。

    问候

        7
  •  1
  •   Henrik Gustafsson    16 年前

    单例和抽象工厂的某种混合可能会使您获得与今天相同的功能,并且具有良好的可测试性,但这会添加大量的锅炉板代码,因此最好尝试完全重构静态内容,或者至少可以使用一些不太复杂的解决方案。

        8
  •  1
  •   martinatime    16 年前

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

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

    然后您可以在不同的测试用例中使用它们

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

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

    分别地

        9
  •  0
  •   KidCrippler    8 年前

    这不是一个真正的答案,但只是想知道——难道没有任何方法可以“逆转”对你的要求吗 Mockit.redefineMethods ?
    如果不存在这样的显式方法,是否应该以下面的方式再次执行它?

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

    如果存在这样的方法,则可以在类的 @AfterClass 方法、步骤和测试 ClassWithStaticInitTest 使用来自同一JVM的“原始”静态初始值设定项块,就像什么都没有改变一样。

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

        10
  •  0
  •   Sebastian Luna    4 年前

    您可以使用PowerMock执行私有方法调用,如下所示:

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