JUnit:仅使用静态方法测试助手类

Posted

技术标签:

【中文标题】JUnit:仅使用静态方法测试助手类【英文标题】:JUnit: testing helper class with only static methods 【发布时间】:2012-03-30 19:22:01 【问题描述】:

我正在使用 JUnit4 和 Cobertura 测试一个只有静态方法的辅助类。测试方法很容易,并且已经完成。

但是,cobertura 表明该类没有完全被测试覆盖,因为它没有在任何地方实例化。

我不想创建这个类的实例(它是一个辅助类),所以第一个解决方案是隐藏构造函数(这通常是辅助类的好方法)。

然后 cobertura 抱怨空的私有构造函数没有被测试覆盖。

对于这种情况,是否有任何解决方案可以实现 100% 的代码覆盖率?

高层管理需要代码覆盖率(在这种情况下),所以对我来说,为这个特定的课程获得 100% 的覆盖率是非常有帮助的。

【问题讨论】:

【参考方案1】:

有几种解决方案:

    您可以添加公共构造函数并从测试中调用它。虽然它没有意义,但它也没有伤害(很多)。

    创建一个虚拟静态实例(您可以在此处调用私有构造函数)。丑陋,但您可以为该字段命名以传达您的意图(JUST_TO_SILENCE_COBERTURA 是个好名字)。

    您可以让您的测试扩展助手类。这将本质上调用默认构造函数,但您的帮助类不能再是 final

我建议最后一种方法,特别是因为该类不能再是final。如果您的代码的使用者想要添加另一个辅助方法,他们现在可以扩展现有类并接收一个句柄来获取所有辅助方法。这会创建传达意图的辅助方法的耦合(它们属于一起) - 如果辅助类是 final

,这是不可能的

如果您想防止用户意外实例化辅助类,请将其设为abstract,而不是使用隐藏的构造函数。

【讨论】:

我认为第三种方法也是最好的,因为它根本不影响代码(仅测试)。我会采取这种方式。谢谢你的帮助。 你也可以把类抽象化。【参考方案2】:

如果您绝对需要实现 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);

基本上,您在这里所做的是按名称获取类,查找该类类型的构造函数,将其设置为公共(setAccessible 调用),不带参数调用构造函数,然后确保抛出的目标异常是InstantiationException

无论如何,正如您所说,这里的 100% 代码覆盖率要求有点让人头疼,但听起来您无法掌控,因此您无能为力。我实际上在自己的代码中使用了与上述类似的方法,我确实发现它是有益的,但不是从测试的角度来看。相反,它只是帮助我比以前了解更多关于反射的知识:)

【讨论】:

为什么在 catch 块中存在“返回”?测试将始终返回 true。 ;) 固定 - 3 年以上,没有人注意到 :)【参考方案3】:

在所有情况下都获得 100% 的覆盖率很好,但在某些情况下这是不可能的。当然,如果您有一个从未实例化的类,Cobertura 会将其视为不完整的测试覆盖,因为这些代码行实际上在该类中,但它们没有经过测试。

事实是你永远不会调用私有构造函数(我假设你已经通过将构造函数设为私有来隐藏构造函数),所以我不会打扰。测试应该是为了得到你所期望的,虽然我同意 100% 的覆盖率很好,但在某些情况下(像这样)这没有用。

也请查看100% Code Coverage。

【讨论】:

有时顶层管理需要代码覆盖率(在这种情况下是这样),所以对我来说,为这个特定的类获得 100% 的覆盖率是非常有帮助的。是的 - 我知道这种方法的荒谬性。但是 - 感谢您提供文章链接。【参考方案4】:

没有。

除非您显式调用私有构造函数(这将是糟糕的代码),否则您将无法覆盖这些行。

【讨论】:

【参考方案5】:

在我的案例中,lombok @UtilityClass 可以将代码覆盖率提高到 100%。

【讨论】:

【参考方案6】:

您可以跳过 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:仅使用静态方法测试助手类的主要内容,如果未能解决你的问题,请参考以下文章

java中的单元测试私有和静态方法

Junit单元测试

JUnit4 中静态内部测试类的嵌套测试设置

PowerMock:模拟仅影响一个测试的静态方法

JUnit Test Assert

junit常用及junit联合spring