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

Java JUnit:仅使用静态方法测试助手类,java,junit,cobertura,Java,Junit,Cobertura,我正在用JUnit4和Cobertura测试一个只使用静态方法的助手类。测试方法很简单,而且已经完成了 然而,cobertura表明该类没有完全被测试覆盖,因为它没有在任何地方实例化 我不想创建这个类的实例(它是一个helper类),所以第一个解决方案是隐藏构造函数(这通常是helper类的好方法) 然后cobertura抱怨空的私有构造函数没有被测试覆盖 对于这种情况,是否有任何解决方案可以实现100%的代码覆盖率 代码覆盖率需要来自顶级管理层(在本例中),因此对于我来说,获得这个特定类的10

我正在用JUnit4和Cobertura测试一个只使用静态方法的助手类。测试方法很简单,而且已经完成了

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

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

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

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

代码覆盖率需要来自顶级管理层(在本例中),因此对于我来说,获得这个特定类的100%覆盖率是非常有帮助的

没有


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

有几种解决方案:

  • 您可以添加公共构造函数并从测试中调用它。虽然这没什么意义,但也没什么害处

  • 创建一个虚拟静态实例(您可以在这里调用私有构造函数)。难看,但你可以给字段起一个名字来表达你的意图(
    JUST_to_SILENCE_COBERTURA
    是个好名字)

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

  • 我建议使用最后一种方法,尤其是因为类不能再是
    final
    。如果代码的使用者想要添加另一个助手方法,他们现在可以扩展现有的类并接收一个句柄来访问所有的助手方法。这将创建一个传递意图的助手方法的耦合(这些方法属于一起)——如果助手类是
    final


    如果要防止用户意外实例化helper类,请将其抽象为
    而不是使用隐藏构造函数。

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

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


    另外,请看一看。

    如果您确实需要实现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 void Test_Constructor_Throws_Exception()抛出IllegalAccessException,实例化Exception{
    最终类别cls=Myutilities.Class;
    最终构造函数c=cls.getDeclaredConstructors()[0];
    c、 setAccessible(true);
    Throwable targetException=null;
    试一试{
    c、 newInstance((对象[])null);
    }捕获(调用targetException ite){
    targetException=ite.getTargetException();
    }
    assertNotNull(targetException);
    assertEquals(targetException.getClass(),实例化Exception.class);
    }
    
    基本上,您在这里要做的是按名称获取类,查找该类类型上的构造函数,将其设置为public(调用
    setAccessible
    ),不带参数地调用构造函数,然后确保引发的目标异常是
    实例化异常


    不管怎样,正如你所说,这里的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");
        }
        ... 
    }
    
    公共类HelperClassTest{
    @测试(预期=IllegalAccessException.class)
    public void ctorShouldBePrivate()抛出实例化异常,IllegalAccessException{
    HelperClass.class.newInstance();
    }
    @试验
    当通过ReflectionUnsupportedOperationExceptionShouldBethrown()引发InstanceException、IllegaAccess Exception、NoSuchMethodException、InvocationTargetException时,为public void{
    构造函数=HelperClass.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    试一试{
    构造函数。newInstance();
    失败(“预计会出现异常”);
    }捕获(调用TargetException e){
    断言(e.getCause(),是(instanceOf(UnsupportedOperationException.class));
    }
    }
    ...
    }
    公共类助手类{
    普里瓦