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

Java 如何向私有构造函数添加测试覆盖率?,java,testing,code-coverage,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 =

代码如下:

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说类的私有构造函数的代码覆盖率为零。我们如何才能将测试覆盖率添加到这样一个私有构造函数中呢?

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

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


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

有时,Cobertura会将不打算执行的代码标记为“未覆盖”,这并没有错。为什么您关心的是
99%
覆盖率而不是
100%

虽然从技术上讲,您仍然可以通过反射调用该构造函数,但在我看来(在本例中)这听起来非常错误。

您不能


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

我不知道Cobertura,但我使用Clover,它有一种添加模式匹配排除的方法。例如,我的模式不包括apache commons日志行,因此它们不计入覆盖范围。

我不完全同意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();
}
@测试
public void TestConstructorPrivate()抛出NoSuchMethodException、IllegaAccessException、InvocationTargetException、InstanceionException{
构造函数=Foo.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers());
constructor.setAccessible(true);
构造函数。newInstance();
}

另一个选项是创建与以下代码类似的静态初始值设定项

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

  // real ops
}
这样,私有构造函数被认为是经过测试的,运行时开销基本上是不可测量的。我这样做是为了使用EclEmma获得100%的覆盖率,但很可能它适用于所有覆盖率工具。
当然,这种解决方案的缺点是,您编写生产代码(静态初始值设定项)只是为了进行测试

测试不起任何作用的代码背后的理由是要实现100%的代码覆盖率,并注意代码何时被删除 覆盖率下降。否则人们可能会想,嘿,我已经没有100%的代码覆盖率了,但这可能是因为 我的私人构造器。这使得发现未测试的方法很容易,而不必检查它是否只是一个私有构造函数。随着代码库的增长,当看到100%而不是99%时,你会感觉到一种温暖的感觉

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

在一个完美的世界中,所有代码覆盖率工具都会忽略属于最终类的私有构造函数,因为构造函数是作为“安全”度量存在的:)
我将使用以下代码:
    @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());
        }
    }
@测试
public void callPrivateConstructorsForCodeCoverage()引发SecurityException、NoSuchMethodException、IllegalArgumentException、InstanceionException、IllegalAccessException、InvocationTargetException
{
类[]classesToConstruct={Foo.Class};
用于(类类别:类结构)
{
构造函数=clazz.getDeclaredConstructor();
constructor.setAccessible(true);
assertNotNull(constructor.newInstance());
}

}
然后在运行时向数组中添加类。

最后,有了解决方案

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

为了满足CheckStyle,我将private作为静态实用程序函数类的构造函数。但就像原来的海报一样,我让科贝图拉抱怨考试。起初我尝试了这种方法,但这并不影响覆盖率报告,因为构造函数从未实际执行过。因此,所有这些测试实际上都是在构造函数保持私有状态的情况下进行的,并且在随后的测试中通过可访问性检查使其变得多余

@Test(expected=IllegalAccessException.class)
public void testConstructorPrivate() throws Exception {
    MyUtilityClass.class.newInstance();
    fail("Utility class constructor should be private");
}
我同意Javid Jamae的建议,使用了反射,但添加了断言,以捕获任何干扰被测试类的人(并将测试命名为表示高级别的邪恶)


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

虽然它不一定用于覆盖率,但我创建此方法是为了验证实用程序类定义良好,并进行了一些覆盖

/**
 * 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);
        }
    }
}
/**
*验证工具类是否定义良好。
* 
*@param-clazz
*要验证的实用程序类。
*/
公共静态无效资产可用性类别良好定义(最终类别类别类别)
抛出NoSuchMethodException、InvocationTargetException、,
实例化异常,非法访问异常{
Assert.assertTrue(“类必须是最终的”,
Modifier.isFinal(clazz.getModifiers());
Assert.assertEquals(“必须只有一个构造函数”,1,
clazz.getDeclaredConstructors().length);
最终建造师
/**
 * 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);
        }
    }
}
@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
}
<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
</clover-setup>
<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>
<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+ *( *) .*"/>
</clover-setup>
@Test
public void testTestPrivateConstructor() {
    Constructor<Test> cnt;
    try {
        cnt = Test.class.getDeclaredConstructor();
        cnt.setAccessible(true);

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

public interface Foo {

  public static int bar() {
    return 1;
  }
}
<cobertura-instrument ignoreTrivial="true" />
cobertura {
    coverageIgnoreTrivial = true
}
<configuration>
  <instrumentation>
    <ignoreTrivial>true</ignoreTrivial>                 
  </instrumentation>
</configuration>
@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
}