JaCoCo и пропущенный охват частного конструктора по умолчанию

Я хотел бы увидеть пример, чтобы JaCoCo не сообщал о частных пустых конструкторах как непокрытый код в классе Java.

В конфигурации плагина maven у меня есть

   <rule>
     <element>CLASS</element>
       <excludes>
         <exclude>JAVAC.SYNTHCLASS</exclude>
         <exclude>JAVAC.SYNTHMETH</exclude>
       </excludes>
     </element>
   </rule>

Разве нет ничего похожего на конструктора?

6 ответов

Решение

Это не поддерживается Официальная документация гласит:

Фильтры для кода, где выполнение теста сомнительно или невозможно по проекту

  • Частные, пустые конструкторы по умолчанию - при условии, что к ним нет обращений
  • Простые добытчики и сеттеры
  • Блоки, которые выбрасывают AssertionErrors - Весь блок следует игнорировать, если условие (если! Assertion выдает новый AssertionError)

см. также: https://github.com/jacoco/jacoco/issues/298

Обновление: это было исправлено в https://github.com/jacoco/jacoco/pull/529 и должно быть в 0.8.0.

Для этого случая использования отражение вполне приемлемо, существует несколько хорошо известных классов. Нижеследующий код может быть использован с автоматическим определением класса на основе имени. Для образцов классов ".*Factory" с дополнительными утверждениями.

@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

Фильтры для кода, где выполнение теста сомнительно или невозможно по проекту

Закрытые пустые конструкторы, у которых нет аргументов - Готово

Вы можете найти детали здесь.

Это не решает существенную проблему, заключающуюся в том, что пустые частные конструкторы не должны нуждаться в покрытии, но для того, чтобы на самом деле создать покрытие отчета 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){}
    }

Поскольку 0.8.0 еще не выпущен, я создал средство сравнения Hamcrest, которое проверяет, является ли класс служебным классом, и дополнительно вызывает приватный конструктор, используя отражение (только для целей покрытия кода).

https://github.com/piotrpolak/android-http-server/blob/master/http/src/test/java/ro/polak/http/utilities/IOUtilitiesTest.java

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()));
    }
}

https://github.com/piotrpolak/android-http-server/blob/master/http/src/test/java/ro/polak/http/ExtraMarchers.java

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;
        }
    }
}
Другие вопросы по тегам