簡體   English   中英

Mocking Static Java 中的塊

[英]Mocking Static Blocks in Java

我對 Java 的座右銘是“僅僅因為 Java 有 static 塊,這並不意味着你應該使用它們。” 撇開玩笑不談,Java 中有很多技巧讓測試成為一場噩夢。 我最討厭的兩個是匿名類和 Static 塊。 我們有很多使用 Static 塊的遺留代碼,這些是我們推動編寫單元測試的惱人點之一。 我們的目標是能夠以最少的代碼更改為依賴於此 static 初始化的類編寫單元測試。

到目前為止,我對同事的建議是將 static 塊的主體移動到私有 static 方法中,並將其命名為staticInit 然后可以從 static 塊中調用此方法。 對於依賴此 class 的另一個 class 的單元測試,可以輕松地使用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);
  }
}

然而,這種解決方案也有其自身的問題。 您不能在同一個 JVM 上運行DependentClassTestClassWithStaticInitTest ,因為您實際上希望 static 塊為ClassWithStaticInitTest運行。

完成這項任務的方法是什么? 或者任何更好的、非基於 JMockit 的解決方案,您認為它們會更干凈?

PowerMock是另一個擴展 EasyMock 和 Mockito 的模擬框架。 使用 PowerMock,您可以輕松地從 class 中刪除不需要的行為,例如 static 初始化程序。 在您的示例中,您只需將以下注釋添加到您的 JUnit 測試用例中:

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

PowerMock 不使用 Java 代理,因此不需要修改 JVM 啟動參數。 您只需添加 jar 文件和上述注釋即可。

有時,我會在我的代碼所依賴的類中找到 static 初始化程序。 如果我無法重構代碼,我使用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...
}

閱讀有關抑制不良行為的更多信息。

免責聲明:PowerMock 是我的兩個同事開發的開源項目。

這將進入更“高級”的 JMockit。 事實證明,您可以通過創建public void $clinit()方法在 JMockit 中重新定義 static 初始化塊。 所以,而不是做這個改變

public class ClassWithStaticInit {
  static {
    staticInit();
  }

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

我們不妨保留ClassWithStaticInit並在MockClassWithStaticInit中執行以下操作:

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

這實際上將允許我們不對現有類進行任何更改。

在我看來,您正在治療一種症狀:依賴於 static 初始化的糟糕設計。 也許一些重構是真正的解決方案。 聽起來您已經對您的staticInit() function 進行了一些重構,但可能需要從構造函數調用 function,而不是從 ZA81259CEF8E959C3224DF1D456E 初始化程序調用。 如果您可以取消 static 初始化程序期間,您會過得更好。 只有你可以做出這個決定(我看不到你的代碼庫),但一些重構肯定會有所幫助。

至於mocking,我用的是EasyMock,但我也遇到了同樣的問題。 遺留代碼中 static 初始化程序的副作用使測試變得困難。 我們的答案是重構 static 初始化程序。

當我遇到這個問題時,我通常會做你描述的同樣的事情,除了我將 static 方法保護起來,以便我可以手動調用它。 最重要的是,我確保可以多次調用該方法而不會出現問題(否則就測試而言,它並不比 static 初始化程序好)。

這工作得相當好,我實際上可以測試 static 初始化方法是否符合我的期望/希望它做的事情。 有時,擁有一些 static 初始化代碼是最簡單的,而構建一個過於復雜的系統來替換它是不值得的。

當我使用這種機制時,我確保記錄受保護的方法僅用於測試目的,希望它不會被其他開發人員使用。 這當然可能不是一個可行的解決方案,例如,如果類的接口是外部可見的(或者作為其他團隊的某種子組件,或者作為公共框架)。 不過,這是解決問題的簡單方法,並且不需要設置第三方庫(我喜歡)。

您可以在 Groovy 中編寫測試代碼,並使用元編程輕松模擬 static 方法。

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

Math.max 1, 2

如果你不能使用 Groovy,你真的需要重構代碼(也許注入類似初始化器的東西)。

親切的問候

我想你真的想要某種工廠而不是 static 初始化程序。

singleton 和抽象工廠的一些組合可能能夠為您提供與今天相同的功能,並且具有良好的可測試性,但這會添加相當多的樣板代碼,因此嘗試重構可能會更好static 完全消失,或者如果您至少可以使用一些不太復雜的解決方案。

不過,如果沒有看到您的代碼,很難判斷它是否可能。

我對 Mock 框架不是很了解,所以如果我錯了請糾正我,但你不能有兩個不同的 Mock 對象來涵蓋你提到的情況嗎?

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);
}

分別。

不是真正的答案,只是想知道 - 有沒有辦法“逆轉”對Mockit.redefineMethods的調用?
如果不存在這樣的顯式方法,不應該以下列方式再次執行它嗎?

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

如果存在這樣的方法,您可以在類的@AfterClass方法中執行它,並使用“原始”static 初始化程序塊測試ClassWithStaticInitTest ,就好像沒有任何變化一樣,從同一個 JVM。

不過,這只是一種預感,所以我可能會遺漏一些東西。

您可以使用 PowerMock 執行私有方法調用,例如:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM