如何将测试覆盖率添加到私有构造函数?

时间:2010-12-23 15:43:47

标签: java testing code-coverage

这是代码:

package com.XXX;
public final class Foo {
  private Foo() {
    // intentionally empty
  }
  public static int bar() {
    return 1;
  }
}

这是测试:

package com.XXX;
public FooTest {
  @Test 
  void testValidatesThatBarWorks() {
    int result = Foo.bar();
    assertEquals(1, result);
  }
  @Test(expected = java.lang.IllegalAccessException.class)
  void testValidatesThatClassFooIsNotInstantiable() {
    Class cls = Class.forName("com.XXX.Foo");
    cls.newInstance(); // exception here
  }
}

工作正常,课程经过测试。但Cobertura表示,该类私有构造函数的代码覆盖率为零。我们如何为这样的私有构造函数添加测试覆盖率?

18 个答案:

答案 0 :(得分:127)

我不完全赞同Jon Skeet。我认为,如果你可以轻松获得覆盖率并消除覆盖率报告中的噪音,那么你应该这样做。告诉你的覆盖工具忽略构造函数,或者把理想主义放在一边,写下面的测试并完成它:

@Test
public void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  Constructor<Foo> constructor = Foo.class.getDeclaredConstructor();
  assertTrue(Modifier.isPrivate(constructor.getModifiers()));
  constructor.setAccessible(true);
  constructor.newInstance();
}

答案 1 :(得分:75)

嗯,有很多方法可以使用反射等 - 但它真的值得吗?这是一个永远不会被称为的构造函数,对吗?

如果有一个注释或任何类似的东西你可以添加到类中以使Cobertura明白它不会被调用,那么这样做:我认为值得通过箍来人为地增加覆盖率。

编辑:如果无法做到这一点,只需稍微减少覆盖范围。请记住,覆盖范围应该是对有用的东西 - 你应该负责该工具,而不是相反。

答案 2 :(得分:73)

虽然它不一定用于覆盖,但是我创建了这个方法来验证实用程序类是否定义良好并且还做了一些覆盖。

/**
 * Verifies that a utility class is well defined.
 * 
 * @param clazz
 *            utility class to verify.
 */
public static void assertUtilityClassWellDefined(final Class<?> clazz)
        throws NoSuchMethodException, InvocationTargetException,
        InstantiationException, IllegalAccessException {
    Assert.assertTrue("class must be final",
            Modifier.isFinal(clazz.getModifiers()));
    Assert.assertEquals("There must be only one constructor", 1,
            clazz.getDeclaredConstructors().length);
    final Constructor<?> constructor = clazz.getDeclaredConstructor();
    if (constructor.isAccessible() || 
                !Modifier.isPrivate(constructor.getModifiers())) {
        Assert.fail("constructor is not private");
    }
    constructor.setAccessible(true);
    constructor.newInstance();
    constructor.setAccessible(false);
    for (final Method method : clazz.getMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && method.getDeclaringClass().equals(clazz)) {
            Assert.fail("there exists a non-static method:" + method);
        }
    }
}

我已将完整的代码和示例放在https://github.com/trajano/maven-jee6/tree/master/maven-jee6-test

答案 3 :(得分:17)

我私有了我的静态实用函数类的构造函数,以满足CheckStyle。但就像原版海报一样,我让Cobertura抱怨测试。起初我尝试了这种方法,但这不会影响覆盖率报告,因为构造函数从未实际执行过。所以所有这些测试都是如果构造函数保持私有 - 并且在后续测试中通过可访问性检查使这变得多余。

@Test(expected=IllegalAccessException.class)
public void testConstructorPrivate() throws Exception {
    MyUtilityClass.class.newInstance();
    fail("Utility class constructor should be private");
}

我选择了Javid Jamae的建议并使用了反思,但添加了断言以捕捉任何搞乱被测试类的人(并将其命名为测试以指示高级邪恶)。

@Test
public void evilConstructorInaccessibilityTest() throws Exception {
    Constructor[] ctors = MyUtilityClass.class.getDeclaredConstructors();
    assertEquals("Utility class should only have one constructor",
            1, ctors.length);
    Constructor ctor = ctors[0];
    assertFalse("Utility class constructor should be inaccessible", 
            ctor.isAccessible());
    ctor.setAccessible(true); // obviously we'd never do this in production
    assertEquals("You'd expect the construct to return the expected type",
            MyUtilityClass.class, ctor.newInstance().getClass());
}

这太过分了,但我必须承认我喜欢100%方法覆盖的温暖模糊感。

答案 4 :(得分:8)

使用 Java 8 ,可以找到其他解决方案。

我假设您只想创建几个公共静态方法的实用程序类。如果您可以使用Java 8,那么您可以改为使用interface

package com.XXX;

public interface Foo {

  public static int bar() {
    return 1;
  }
}

Cobertura没有建设者也没有抱怨。现在,您只需要测试您真正关心的行。

答案 5 :(得分:5)

测试无法执行任何操作的代码背后的原因是实现100%的代码覆盖率并注意代码 覆盖率下降。否则我总是会想,嘿,我不再拥有100%的代码覆盖率,但它可能是因为 我的私人建设者。这使得很容易发现未经测试的方法,而无需检查它只是一个私有构造函数。随着您的代码库的增长,您实际上会感受到一种温暖的感觉,即100%而不是99%。

IMO最好在这里使用反射,否则您将不得不获得更好的代码覆盖工具忽略这些构造函数或以某种方式告诉代码覆盖工具忽略该方法(可能是注释或配置文件),因为那样你将坚持使用特定的代码覆盖工具。

在一个完美的世界中,所有代码覆盖工具都会忽略属于最终类的私有构造函数,因为构造函数在那里作为“安全”度量而没有别的:) 我会用这个代码:

    @Test
    public void callPrivateConstructorsForCodeCoverage() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
    {
        Class<?>[] classesToConstruct = {Foo.class};
        for(Class<?> clazz : classesToConstruct)
        {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            assertNotNull(constructor.newInstance());
        }
    }
然后只需向数组中添加类。

答案 6 :(得分:5)

较新版本的Cobertura内置支持忽略琐碎的getter / setter / constructors:

https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial

忽略琐碎

忽略琐碎允许排除包含一行代码的构造函数/方法。一些示例包括仅调用超级constrctor,getter / setter方法等。要包含ignore trivial参数,请添加以下内容:

<cobertura-instrument ignoreTrivial="true" />

或在Gradle构建中:

cobertura {
    coverageIgnoreTrivial = true
}

答案 7 :(得分:4)

别。 测试空构造函数有什么意义? 既然cobertura 2.0有一个选项可以忽略这些琐碎的情况(连同setter / getters),你可以通过在cobertura maven插件中添加配置部分来启用maven:

<configuration>
  <instrumentation>
    <ignoreTrivial>true</ignoreTrivial>                 
  </instrumentation>
</configuration>

或者,您可以使用Coverage Annotations@CoverageIgnore

答案 8 :(得分:3)

最后,有解决方案!

public enum Foo {;
  public static int bar() {
    return 1;
  }
}

答案 9 :(得分:1)

我不知道Cobertura,但我使用Clover,它有一种添加模式匹配排除的方法。例如,我的模式排除了apache-commons-logging行,因此它们不计入coverage。

答案 10 :(得分:1)

另一种选择是创建一个类似于以下代码的静态初始化器

class YourClass {
  private YourClass() {
  }
  static {
     new YourClass();
  }

  // real ops
}

这样,私有构造函数被认为是经过测试的,并且运行时开销基本上是不可测量的。我这样做是为了使用EclEmma获得100%的覆盖率,但可能它适用于每个覆盖工具。 当然,这种解决方案的缺点是您只是为了测试目的而编写生产代码(静态初始化程序)。

答案 11 :(得分:1)

ClassUnderTest testClass = Whitebox.invokeConstructor(ClassUnderTest.class);

答案 12 :(得分:0)

有时Cobertura将代码打算不打算执行为“未涵盖”,这没有任何问题。您为什么关注99%覆盖而不是100%

但从技术上讲,你仍然可以用反射来调用那个构造函数,但对我来说这听起来很错误(在这种情况下)。

答案 13 :(得分:0)

我的首选选项:使用龙目岛。

具体地说,是@UtilityClass annotation。 (在撰写本文时,这只能是“实验性的”,但它的功能还不错,并且具有积极的前景,因此很可能很快会升级为稳定版。)

此注释将添加私有构造函数以防止实例化,并使类最终化。与lombok.addLombokGeneratedAnnotation = true中的lombok.config结合使用时,几乎所有测试框架在计算测试覆盖率时都将忽略自动生成的代码,从而使您能够绕过自动生成的代码,而不会受到黑客或反思。 / p>

答案 14 :(得分:0)

@Test
public void testTestPrivateConstructor() {
    Constructor<Test> cnt;
    try {
        cnt = Test.class.getDeclaredConstructor();
        cnt.setAccessible(true);

        cnt.newInstance();
    } catch (Exception e) {
        e.getMessage();
    }
}

Test.java是你的源文件,它有你的私有构造函数

答案 15 :(得分:0)

如果我猜测你问题的意图,我会说:

  1. 您希望对执行实际工作的私有构造函数进行合理检查,并且
  2. 您希望三叶草排除util类的空构造函数。
  3. 对于1,很明显您希望通过工厂方法完成所有初始化。在这种情况下,您的测试应该能够测试构造函数的副作用。这应属于正常的私有方法测试类别。使方法更小,以便它们只做有限数量的确定事物(理想情况下,只做一件事和一件事),然后测试依赖它们的方法。

    例如,如果我的[private]构造函数将我的类的实例字段a设置为5。然后我可以(或者更确切地说)测试它:

    @Test
    public void testInit() {
        MyClass myObj = MyClass.newInstance(); //Or whatever factory method you put
        Assert.assertEquals(5, myObj.getA()); //Or if getA() is private then test some other property/method that relies on a being 5
    }
    

    对于2,如果您具有Util类的设置命名模式,则可以配置clover以排除Util构造函数。例如,在我自己的项目中,我使用这样的东西(因为我们遵循惯例,所有Util类的名称应以Util结尾):

    <clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
        <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
    </clover-setup>
    

    我故意在.*之后遗漏了)因为这样的构造函数不是要抛出异常(它们不是为了做任何事情)。

    当然可能存在第三种情况,您可能希望为非实用类创建一个空构造函数。在这种情况下,我建议您使用构造函数的确切签名放置methodContext

    <clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
        <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
        <methodContext name="myExceptionalClassCtor" regexp="^private MyExceptionalClass()$"/>
    </clover-setup>
    

    如果你有许多这样的特殊类,那么你可以选择修改我建议的通用私有构造函数reg-ex并从中删除Util。在这种情况下,您必须手动确保构造函数的副作用仍然在您的类/项目中的其他方法中进行测试和覆盖。

    <clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
        <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+ *( *) .*"/>
    </clover-setup>
    

答案 16 :(得分:-1)

你不能。

您显然正在创建私有构造函数,以防止实例化仅包含静态方法的类。不要试图覆盖这个构造函数(这需要实例化类),你应该摆脱它,并相信你的开发人员不要向类中添加实例方法。

答案 17 :(得分:-1)

以下内容对我有用Lombok批注@UtilityClass创建的类起作用,该类会自动添加私有构造函数。

@Test
public void testConstructorIsPrivate() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
    Constructor<YOUR_CLASS_NAME> constructor = YOUR_CLASS_NAME.class.getDeclaredConstructor();
    assertTrue(Modifier.isPrivate(constructor.getModifiers())); //this tests that the constructor is private
    constructor.setAccessible(true);
    assertThrows(InvocationTargetException.class, () -> {
        constructor.newInstance();
    }); //this add the full coverage on private constructor
}

尽管当手动构造私有构造函数时,constructor.setAccessible(true)应该可以工作,但带有Lombok注释不起作用,因为它会强制执行。 Constructor.newInstance()实际上测试了构造函数是否被调用,从而完成了对构造函数本身的覆盖。使用assertThrows可以防止测试失败,并且可以管理异常,因为这正是您所期望的错误。 尽管这是一种解决方法,但我不喜欢“线路覆盖率”与“功能/行为覆盖率”的概念,但我们可以在此测试中找到一种感觉。 实际上,您可以确保实用程序类实际上具有私有构造函数,该构造函数在通过reflaction调用时也会正确引发异常。 希望这会有所帮助。