[英]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 上運行DependentClassTest
和ClassWithStaticInitTest
,因為您實際上希望 static 塊為ClassWithStaticInitTest
運行。
完成這項任務的方法是什么? 或者任何更好的、非基於 JMockit 的解決方案,您認為它們會更干凈?
有時,我會在我的代碼所依賴的類中找到 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.