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

yeg*_*256 106 java testing code-coverage

这是代码:

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

这是测试:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Jav*_*mae 134

我并不完全赞同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();
}
Run Code Online (Sandbox Code Playgroud)

  • 但这可以通过*向测试套件添加噪声*来消除覆盖率报告中的噪声.我本来刚刚结束这句话"把理想主义放在一边".:) (24认同)
  • 为了给这个测试任何意义,你应该断言构造函数的访问级别是你期望的. (11认同)
  • 这是错误的:`constructor.isAccessible()`总是返回false,即使在公共构造函数上也是如此.应该使用`assertTrue(Modifier.isPrivate(constructor.getModifiers()));`. (2认同)
  • 如果您使用 JaCoCo,&gt;= 0.7.10 [将忽略空的私有构造函数](https://github.com/jacoco/jacoco/pull/529) (2认同)

Jon*_*eet 81

好吧,有些方法可以使用反射等 - 但是它真的值得吗?这是一个永远不应该被调用的构造函数,对吗?

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

编辑:如果没有办法,只需稍微减少覆盖率.请记住,覆盖范围应该是对有用的东西- 您应该负责该工具,而不是相反.

  • @Vincenzo:然后IMO你在一个简单的数字上放置了太高的值.覆盖率是测试的*指标*.不要成为工具的奴隶.报道的重点是为您提供一定程度的信心,并建议进行额外测试的领域.人为地调用其他未使用的构造函数对这些点中的任何一个都没有帮助. (34认同)
  • 我不想因为这个特殊的构造函数而在整个项目中"略微降低覆盖率". (17认同)
  • @JonSkeet:我完全同意"不要成为工具的奴隶",但要记住每个项目中的每个"缺陷数"并不好闻.如何确保7/9结果是Cobertura限制,而不是程序员?一个新的程序员必须输入每个失败(在大型项目中可能很多)来逐个检查. (16认同)
  • 这不回答这个问题.顺便说一句,一些经理看看覆盖数字.他们不在乎为什么.他们知道85%优于75%. (5认同)
  • @ACV:那么他们“知道”的是不正确的。如果您有两组测试覆盖相同的代码,则无法保证覆盖率更高的测试实际上是更好的测试。我会毫不犹豫地与一位经理争论,他开始应用可疑的质量指标而不了解它们。 (2认同)
  • 测试否则将无法访问的代码的实际用例是达到100%的测试覆盖率,这样就无需再次检查该类。如果覆盖率停留在95%,许多开发人员可能会试图找出原因,只是一遍又一遍地碰到这个问题。 (2认同)

Arc*_*ano 75

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

/**
 * 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1这不仅可以在不欺骗工具的情况下解决问题,而且可以完全测试设置实用程序类的编码标准.我不得不改变辅助功能测试以使用`Modifier.isPrivate`,因为`isAccessible`在某些情况下为私有构造函数返回`true`(模拟库干扰?). (11认同)
  • 我真的想将它添加到JUnit的Assert类中,但不想因为你的工作而受到赞誉.我觉得这很好.在JUnit 4.12+中使用`Assert.utilityClassWellDefined()`会很棒.你考虑过拉动请求吗? (4认同)

Ben*_*rdy 19

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

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

我选择了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());
}
Run Code Online (Sandbox Code Playgroud)

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


Arn*_*cek 9

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

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

package com.XXX;

public interface Foo {

  public static int bar() {
    return 1;
  }
}
Run Code Online (Sandbox Code Playgroud)

Cobertura没有建设者也没有抱怨.现在您只需要测试您真正关心的线条.

  • 然而不幸的是,您不能将接口声明为“最终”接口,以防止任何人对其进行子类化 - 否则这将是最好的方法。 (2认同)

Ric*_*ena 6

以下内容对我来说是使用 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
}
Run Code Online (Sandbox Code Playgroud)

虽然在手动编写私有构造函数时, constructor.setAccessible(true) 应该可以工作,但使用 Lombok 注释不起作用,因为它强制它。Constructor.newInstance() 实际上测试构造函数是否被调用,这完成了构造函数本身的覆盖。使用assertThrows,您可以防止测试失败并管理异常,因为这正是您所期望的错误。尽管这是一种解决方法,而且我不理解“线路覆盖率”与“功能/行为覆盖率”的概念,但我们可以在这个测试中找到意义。事实上,您确信实用程序类实际上有一个私有构造函数,该构造函数在通过反射调用时也可以正确抛出异常。希望这可以帮助。


jon*_*ejj 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());
        }
    }
Run Code Online (Sandbox Code Playgroud) 然后只需向数组添加类即可.


Mik*_*hot 5

较新版本的Cobertura具有内置支持,可忽略琐碎的getter / setter / constructor:

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

忽略琐碎的事

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

<cobertura-instrument ignoreTrivial="true" />
Run Code Online (Sandbox Code Playgroud)

或在Gradle构建中:

cobertura {
    coverageIgnoreTrivial = true
}
Run Code Online (Sandbox Code Playgroud)