簡體   English   中英

JUnit:僅使用靜態方法測試助手類

[英]JUnit: testing helper class with only static methods

我正在使用 JUnit4 和 Cobertura 測試一個只有靜態方法的助手類。 測試方法很容易,並且已經完成。

但是,cobertura 表明該類沒有完全被測試覆蓋,因為它沒有在任何地方實例化。

我不想創建這個類的實例(它是一個輔助類),所以第一個解決方案是隱藏構造函數(這通常是輔助類的好方法)。

然后 cobertura 抱怨空的私有構造函數沒有被測試覆蓋。

對於這種情況,是否有任何解決方案可以實現 100% 的代碼覆蓋率?

頂層管理需要代碼覆蓋率(在這種情況下),所以對我來說,為這個特定的類獲得 100% 的覆蓋率是非常有幫助的。

有幾種解決方案:

  1. 您可以添加一個公共構造函數並從測試中調用它。 雖然它沒有意義,但它也沒有傷害(很多)。

  2. 創建一個虛擬靜態實例(您可以在此處調用私有構造函數)。 丑陋,但您可以為該字段命名來傳達您的意圖( JUST_TO_SILENCE_COBERTURA是一個好名字)。

  3. 您可以讓您的測試擴展助手類。 這將本質上調用默認構造函數,但您的幫助類不能再是final的。

我建議使用最后一種方法,尤其是因為該類不再是final的。 如果您的代碼的使用者想要添加另一個輔助方法,他們現在可以擴展現有類並接收一個句柄來獲取所有輔助方法。 這會創建傳達意圖的輔助方法的耦合(它們屬於一起) - 如果輔助類是final的,這是不可能的

如果您想防止用戶意外實例化輔助類,請將其abstract化,而不是使用隱藏的構造函數。

如果您絕對需要實現 100% 的代碼覆蓋率 - 其優點可以在其他地方進行辯論 :) - 您可以在測試中使用反射來實現它。 作為習慣,當我實現一個僅靜態的實用程序類時,我添加了一個私有構造函數以確保無法創建該類的實例。 例如:

/** 
 * Constructs a new MyUtilities.
 * @throws InstantiationException
 */
private MyUtilities() throws InstantiationException
{
    throw new InstantiationException("Instances of this type are forbidden.");
}

那么你的測試可能看起來像這樣:

@Test
public void Test_Constructor_Throws_Exception() throws IllegalAccessException, InstantiationException {
    final Class<?> cls = MyUtilties.class;
    final Constructor<?> c = cls.getDeclaredConstructors()[0];
    c.setAccessible(true);

    Throwable targetException = null;
    try {
        c.newInstance((Object[])null);
    } catch (InvocationTargetException ite) {
        targetException = ite.getTargetException();
    }

    assertNotNull(targetException);
    assertEquals(targetException.getClass(), InstantiationException.class);
}

基本上,您在這里所做的是按名稱獲取類,查找該類類型的構造函數,將其設置為 public( setAccessible調用),不帶參數調用構造函數,然后確保拋出的目標異常是一個InstantiationException

無論如何,正如您所說,這里的 100% 代碼覆蓋率要求有點痛苦,但聽起來它超出了您的控制范圍,因此您無能為力。 我實際上在自己的代碼中使用了與上述類似的方法,我確實發現它是有益的,但不是從測試的角度來看。 相反,它只是幫助我比以前了解更多關於反射的知識:)

在所有情況下獲得 100% 的覆蓋率很好,但在某些情況下這是不可能的。 當然,如果您有一個從未實例化的類,Cobertura 會將其視為不完整的測試覆蓋,因為這些代碼行實際上在類中,但它們沒有經過測試。

事實是您永遠不會調用私有構造函數(我假設您已經通過將構造函數設為私有來隱藏構造函數),所以我不會打擾。 測試應該是關於得到你所期望的,雖然我同意 100% 的覆蓋率很好,但在某些情況下(像這樣)這沒有用。

看看100% 代碼覆蓋率

不。

除非您顯式調用私有構造函數(這將是糟糕的代碼),否則您將無法覆蓋這些行。

在我的案例中, lombok @UtilityClass可以將代碼覆蓋率提高到 100%。

您可以跳過 100% 的覆蓋范圍。 它根本不應該傷害。 但是,如果您正在開發一個非常嚴格的產品,目標是 100% 的覆蓋率,那么您可以使用以下策略:

在您的情況下,缺少的覆蓋是構造函數:您應該測試構造函數的預期行為。

如果不定義構造函數,則允許實例化類(通過默認構造函數)。 您應該測試默認構造函數的行為:在測試中,調用構造函數並測試結果,例如沒有拋出錯誤,或者返回一個有效的實例。

相反,如果作為實用程序類實踐,您還將構造函數定義為private並在構造函數中拋出UnsupportedOperationException 在測試中,您可以斷言該行為。

public class HelperClassTest {
    @Test(expected = IllegalAccessException.class)
    public void ctorShouldBePrivate() throws InstantiationException, IllegalAccessException {
        HelperClass.class.newInstance();
    }

    @Test
    public void whenCtorIsCalledThroughReflectionUnsupportedOperationExceptionShouldBeThrown() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Constructor<HelperClass> constructor = HelperClass.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        try {
            constructor.newInstance();
            fail("Exception is expected");
        } catch (InvocationTargetException e) {
            assertThat(e.getCause(), is(instanceOf(UnsupportedOperationException.class)));
        }
    }
...
}

public class HelperClass{
    private HelperClass() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }
    ... 
}

在 junit 5 (jupiter) 上,當您使用靜態方法向類添加私有構造函數時,覆蓋率為 100%

暫無
暫無

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

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