JaCoCo 和私有默认构造函数的遗漏
JaCoCo and missed coverage of private default constructor
我想看一个示例,以防止 JaCoCo 将私有空构造函数报告为 Java class 中的未覆盖代码。
在我的maven插件配置中
<rule>
<element>CLASS</element>
<excludes>
<exclude>JAVAC.SYNTHCLASS</exclude>
<exclude>JAVAC.SYNTHMETH</exclude>
</excludes>
</element>
</rule>
构造函数没有类似的东西吗?
这是不支持的。 official documentation 表示:
Filters for Code where Test Execution is Questionable or Impossible by Design
- Private, empty default constructors - assuming no calls to it
- Plain getters and setters
- Blocks that throw AssertionErrors - Entire block should be ignored if a condition (if !assertion throw new AssertionError)
另请参阅:https://github.com/jacoco/jacoco/issues/298
更新: 这已在 https://github.com/jacoco/jacoco/pull/529 中修复,应该在 0.8.0 中。
无法关闭该选项。如果您迫切需要满足与覆盖率相关的一些质量门槛,您始终可以使用变通方法并通过反射调用这些私有构造函数。
这并没有解决空私有构造函数不需要覆盖的本质问题,而是要真正让 JaCoCo 报告对空私有构造函数的覆盖,您需要调用它。你是怎样做的?您在静态初始化块中调用它。
public class MyClass {
static {
new MyClass();
}
private MyClass(){}
}
编辑:
事实证明,无法保证要执行的静态初始化块。因此,我们仅限于使用这种方法:
static <T> void callPrivateConstructorIfPresent(Class<T> clazz){
try{
Constructor<T> noArgsConstructor = clazz.getDeclaredConstructor();
if(!noArgsConstructor.isAccessible()){
noArgsConstructor.setAccessible(true);
try {
noArgsConstructor.newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
e.printStackTrace();
}
noArgsConstructor.setAccessible(false);
}
} catch(NoSuchMethodException e){}
}
对于这个用例,反射是完全可以接受的,很少有众所周知的 classes。波纹管代码可以与基于名称的自动 class 检测一起使用。对于带有附加断言的示例“.*Factory”classes。
@Test
public void testCoverage()
throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
coverageSingleton(MySingleton1.class);
coverageSingleton(MySingleton2.class);
}
private <S> void coverageSingleton(Class<S> singletonClass)
throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
final Constructor<S> constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
根据官方文档,它将与 0.8.0
一起发布
Filters for Code where Test Execution is Questionable or Impossible by
Design
Private empty constructors that do not have arguments - Done
您可以找到详细信息 here。
由于 0.8.0 尚未发布,我创建了一个 hamcrest 匹配器来检查 class 是否是一个实用程序 class 并另外使用反射调用私有构造函数(仅用于代码覆盖目的).
package ro.polak.http.utilities;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static ro.polak.http.ExtraMarchers.utilityClass;
public class IOUtilitiesTest {
@Test
public void shouldNotBeInstantiable() {
assertThat(IOUtilities.class, is(utilityClass()));
}
}
package ro.polak.http;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ExtraMarchers {
private static final UtilClassMatcher utilClassMatcher = new UtilClassMatcher();
public static Matcher<? super Class<?>> utilityClass() {
return utilClassMatcher;
}
private static class UtilClassMatcher extends TypeSafeMatcher<Class<?>> {
@Override
protected boolean matchesSafely(Class<?> clazz) {
boolean isUtilityClass = false;
try {
isUtilityClass = isUtilityClass(clazz);
} catch (ClassNotFoundException | InstantiationException e) {
// Swallowed
}
// This code will attempt to call empty constructor to generate code coverage
if (isUtilityClass) {
callPrivateConstructor(clazz);
}
return isUtilityClass;
}
@Override
protected void describeMismatchSafely(Class<?> clazz, Description mismatchDescription) {
if (clazz == null) {
super.describeMismatch(clazz, mismatchDescription);
} else {
mismatchDescription.appendText("The class " + clazz.getCanonicalName() + " is not an utility class.");
boolean isNonUtilityClass = true;
try {
isNonUtilityClass = !isUtilityClass(clazz);
} catch (ClassNotFoundException e) {
mismatchDescription.appendText(" The class is not found. " + e);
} catch (InstantiationException e) {
mismatchDescription.appendText(" The class can not be instantiated. " + e);
}
if (isNonUtilityClass) {
mismatchDescription.appendText(" The class should not be instantiable.");
}
}
}
@Override
public void describeTo(Description description) {
}
private void callPrivateConstructor(Class clazz) {
try {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
} catch (NoSuchMethodException | IllegalAccessException |
InstantiationException | InvocationTargetException e) {
// Swallowed
}
}
private boolean isUtilityClass(Class clazz) throws ClassNotFoundException, InstantiationException {
boolean hasPrivateConstructor = false;
try {
clazz.newInstance();
} catch (IllegalAccessException e) {
hasPrivateConstructor = true;
}
return hasPrivateConstructor;
}
}
}
我想看一个示例,以防止 JaCoCo 将私有空构造函数报告为 Java class 中的未覆盖代码。
在我的maven插件配置中
<rule>
<element>CLASS</element>
<excludes>
<exclude>JAVAC.SYNTHCLASS</exclude>
<exclude>JAVAC.SYNTHMETH</exclude>
</excludes>
</element>
</rule>
构造函数没有类似的东西吗?
这是不支持的。 official documentation 表示:
Filters for Code where Test Execution is Questionable or Impossible by Design
- Private, empty default constructors - assuming no calls to it
- Plain getters and setters
- Blocks that throw AssertionErrors - Entire block should be ignored if a condition (if !assertion throw new AssertionError)
另请参阅:https://github.com/jacoco/jacoco/issues/298
更新: 这已在 https://github.com/jacoco/jacoco/pull/529 中修复,应该在 0.8.0 中。
无法关闭该选项。如果您迫切需要满足与覆盖率相关的一些质量门槛,您始终可以使用变通方法并通过反射调用这些私有构造函数。
这并没有解决空私有构造函数不需要覆盖的本质问题,而是要真正让 JaCoCo 报告对空私有构造函数的覆盖,您需要调用它。你是怎样做的?您在静态初始化块中调用它。
public class MyClass {
static {
new MyClass();
}
private MyClass(){}
}
编辑: 事实证明,无法保证要执行的静态初始化块。因此,我们仅限于使用这种方法:
static <T> void callPrivateConstructorIfPresent(Class<T> clazz){
try{
Constructor<T> noArgsConstructor = clazz.getDeclaredConstructor();
if(!noArgsConstructor.isAccessible()){
noArgsConstructor.setAccessible(true);
try {
noArgsConstructor.newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
e.printStackTrace();
}
noArgsConstructor.setAccessible(false);
}
} catch(NoSuchMethodException e){}
}
对于这个用例,反射是完全可以接受的,很少有众所周知的 classes。波纹管代码可以与基于名称的自动 class 检测一起使用。对于带有附加断言的示例“.*Factory”classes。
@Test
public void testCoverage()
throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
coverageSingleton(MySingleton1.class);
coverageSingleton(MySingleton2.class);
}
private <S> void coverageSingleton(Class<S> singletonClass)
throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
final Constructor<S> constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
根据官方文档,它将与 0.8.0
Filters for Code where Test Execution is Questionable or Impossible by Design
Private empty constructors that do not have arguments - Done
您可以找到详细信息 here。
由于 0.8.0 尚未发布,我创建了一个 hamcrest 匹配器来检查 class 是否是一个实用程序 class 并另外使用反射调用私有构造函数(仅用于代码覆盖目的).
package ro.polak.http.utilities;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static ro.polak.http.ExtraMarchers.utilityClass;
public class IOUtilitiesTest {
@Test
public void shouldNotBeInstantiable() {
assertThat(IOUtilities.class, is(utilityClass()));
}
}
package ro.polak.http;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ExtraMarchers {
private static final UtilClassMatcher utilClassMatcher = new UtilClassMatcher();
public static Matcher<? super Class<?>> utilityClass() {
return utilClassMatcher;
}
private static class UtilClassMatcher extends TypeSafeMatcher<Class<?>> {
@Override
protected boolean matchesSafely(Class<?> clazz) {
boolean isUtilityClass = false;
try {
isUtilityClass = isUtilityClass(clazz);
} catch (ClassNotFoundException | InstantiationException e) {
// Swallowed
}
// This code will attempt to call empty constructor to generate code coverage
if (isUtilityClass) {
callPrivateConstructor(clazz);
}
return isUtilityClass;
}
@Override
protected void describeMismatchSafely(Class<?> clazz, Description mismatchDescription) {
if (clazz == null) {
super.describeMismatch(clazz, mismatchDescription);
} else {
mismatchDescription.appendText("The class " + clazz.getCanonicalName() + " is not an utility class.");
boolean isNonUtilityClass = true;
try {
isNonUtilityClass = !isUtilityClass(clazz);
} catch (ClassNotFoundException e) {
mismatchDescription.appendText(" The class is not found. " + e);
} catch (InstantiationException e) {
mismatchDescription.appendText(" The class can not be instantiated. " + e);
}
if (isNonUtilityClass) {
mismatchDescription.appendText(" The class should not be instantiable.");
}
}
}
@Override
public void describeTo(Description description) {
}
private void callPrivateConstructor(Class clazz) {
try {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
} catch (NoSuchMethodException | IllegalAccessException |
InstantiationException | InvocationTargetException e) {
// Swallowed
}
}
private boolean isUtilityClass(Class clazz) throws ClassNotFoundException, InstantiationException {
boolean hasPrivateConstructor = false;
try {
clazz.newInstance();
} catch (IllegalAccessException e) {
hasPrivateConstructor = true;
}
return hasPrivateConstructor;
}
}
}