如何将testing覆盖率添加到私有构造函数?
这是代码:
package com.XXX; public final class Foo { private Foo() { // intentionally empty } public static int bar() { return 1; } }
这是testing:
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 } }
工作正常,class级进行testing。 但是Cobertura说这个类的私有构造函数的代码覆盖率是零。 我们如何将testing覆盖率添加到这样的私人构造函数?
那么,有很多方法可以使用reflection等 – 但它真的值得吗? 这是一个不应该被调用的构造函数,对吧?
如果有一个注释或类似的东西,你可以添加到课堂上让Cobertura明白它不会被调用,那么这样做:我不认为值得通过人为增加覆盖率。
编辑:如果没有办法做到这一点,只是略有减less的覆盖面。 请记住,覆盖范围是对你有用的东西 – 你应该负责工具,而不是相反。
我不完全赞同Jon Skeet。 我认为,如果您可以轻松取胜,为您提供报道并消除报道报道中的噪音,那么您应该这样做。 要么告诉你的覆盖工具忽略构造函数,要么把理想主义放在一边,写下面的testing,并用它来完成:
@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(); }
虽然这不一定是覆盖范围,但是我创build了这个方法来validation实用程序类是否定义良好,并进行一些覆盖。
/** * 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
我已经使我的类静态实用函数的构造函数,以满足CheckStyle。 但是就像原来的海报一样,我让Cobertura抱怨这个testing。 起初,我尝试了这种方法,但是这不会影响覆盖率报告,因为构造函数从未被实际执行过。 所以所有这些testing都是在构造函数保持私有的情况下进行的 – 在随后的testing中,通过可访问性检查,这会变得冗余。
@Test(expected=IllegalAccessException.class) public void testConstructorPrivate() throws Exception { MyUtilityClass.class.newInstance(); fail("Utility class constructor should be private"); }
我和Javid Jamae的build议一起使用,并使用了反思,但是join了一些断言,让任何人搞乱了正在testing的类(并且把这个testing命名为“高级恶魔”)。
@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%方法覆盖的温暖的模糊感觉。
testing代码背后的原因是要达到100%的代码覆盖率,并注意代码覆盖率何时下降。 否则,人们总是会想,嘿,我没有100%的代码覆盖率,但可能是因为我的私人构造函数。 这使得很容易发现未经testing的方法,而不必检查它是否是一个私有的构造函数。 随着你的代码库的增长,你会觉得100%而不是99%的温暖感。
海事组织最好在这里使用reflection,否则你将不得不得到一个更好的代码覆盖工具,忽略这些构造函数或以某种方式告诉代码覆盖工具忽略该方法(可能是一个注释或configuration文件),因为那样你会卡住使用特定的代码覆盖工具。
在完美的世界中,所有代码覆盖工具都会忽略属于最终类的私有构造函数,因为构造函数在那里是作为“安全”度量的,没有别的东西:)
我会用这个代码:
@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()); } }
然后,只要将数组添加到数组中即可。
使用Java 8 ,可以find其他解决scheme。
我假设你只是想用less数的公共静态方法创build工具类。 如果你可以使用Java 8,那么你可以使用interface
。
package com.XXX; public interface Foo { public static int bar() { return 1; } }
没有build设者,也没有来自Cobertura的抱怨。 现在你只需要testing你真正关心的行。
较新版本的Cobertura内置了对忽略getter / setter / constructor的简单支持:
https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial
忽略微不足道
忽略微不足道允许排除包含一行代码的构造函数/方法的能力。 一些例子只包括对super constrctor的调用,getter / setter方法等。要包含ignore trivial参数,请添加以下内容:
<cobertura-instrument ignoreTrivial="true" />
或者在Gradle构build中:
cobertura { coverageIgnoreTrivial = true }
最后,有解决scheme!
public enum Foo {; public static int bar() { return 1; } }
别。 testing一个空的构造函数有什么意义? 由于cobertura 2.0有一个选项可以忽略这些微不足道的情况(与setter / getters一起),你可以在maven中通过向cobertura maven插件添加configuration部分来启用它:
<configuration> <instrumentation> <ignoreTrivial>true</ignoreTrivial> </instrumentation> </configuration>
或者,您可以使用Coverage Annotations : @CoverageIgnore
。
我不知道Cobertura,但我使用Clover,它有添加模式匹配排除的手段。 例如,我有排除apache-commons-logging行的模式,所以它们不计入覆盖范围。
另一个选项是创build一个类似于以下代码的静态初始化程序
class YourClass { private YourClass() { } static { new YourClass(); } // real ops }
这样私人的构造函数被认为是testing,运行时开销基本上是不可测量的。 我这样做是为了使用EclEmma来获得100%的覆盖率,但是对于每个覆盖率工具来说可能都是可行的。 当然,这个解决scheme的缺点是你只是为了testing目的而编写产品代码(静态初始化器)。
有时候,Cobertura标记的代码并不打算作为“未被覆盖”执行,这没有什么错。 你为什么要关心99%
覆盖而不是100%
?
不过从技术上讲,你仍然可以用reflection来调用构造函数,但是这对我来说听起来是非常错误的(在这种情况下)。
如果我猜测你的问题的意图,我会说:
- 你需要合理的检查做实际工作的私人构造函数
- 您希望三叶草排除util类的空构造函数。
对于1来说,显然你想通过工厂方法来完成所有的初始化。 在这种情况下,你的testing应该能够testing构造函数的副作用。 这应该属于正常的私人方法testing的范畴。 使这些方法变得更小,以便只做有限数量的确定性事物(理想情况下只是一件事,一件事),然后testing依赖它们的方法。
例如,如果我的[私人]构造函数设置我的类的实例字段a
到5
。 那么我可以(或者更确切地说)testing它:
@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类设置了命名模式,则可以将三叶草configuration为排除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>
我故意留下一个.*
后面的)
因为这样的构造函数并不是要抛出exception(它们并不意味着什么)。
当然可以有第三种情况,你可能想要一个非实用类的空构造函数。 在这种情况下,我会build议你把一个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>
如果你有很多这样的特殊类,那么你可以select修改我build议的通用私有构造函数reg-ex,并从中删除Util
。 在这种情况下,您将不得不手动确保您的构造函数的副作用仍然在您的类/项目中被其他方法testing和覆盖。
<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(); } }
Test.java是你的源文件,它有你的私有构造函数
你不能。
你显然创build了私有构造函数,以防止只包含静态方法的类的实例化。 而不是试图得到这个构造函数的覆盖(这将需要类实例化),你应该摆脱它,并相信你的开发人员不要向类中添加实例方法。