Как добавить тестовое покрытие для частного конструктора? - PullRequest
101 голосов
/ 23 декабря 2010

Это код:

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
  }
}

Работает нормально, класс протестирован.Но Кобертура говорит, что у частного конструктора класса нет покрытия кода.Как мы можем добавить тестовое покрытие для такого частного конструктора?

Ответы [ 16 ]

129 голосов
/ 08 февраля 2011

Я не совсем согласен с Джоном Скитом.Я думаю, что если вы можете легко выиграть, чтобы предоставить вам покрытие и устранить шумы в вашем отчете о покрытии, то вам следует это сделать.Либо скажите вашему инструменту покрытия игнорировать конструктор, либо отложите идеализм в сторону и напишите следующий тест и покончите с ним:

@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();
}
77 голосов
/ 23 декабря 2010

Ну, есть способы, которыми вы могли бы потенциально использовать отражение и т. Д., Но действительно ли это того стоит?Это конструктор, который никогда не должен называться , верно?

Если есть аннотация или что-то подобное, что вы можете добавить в класс, чтобы Кобертура понимала, что он не будет вызвансделать это: я не думаю, что стоит обойтись, чтобы искусственно добавить покрытие.

РЕДАКТИРОВАТЬ: Если нет никакого способа сделать это, просто жить с немного сокращенным покрытием.Помните, что покрытие должно быть чем-то, что полезно для вас - вы должны отвечать за инструмент, а не наоборот.

73 голосов
/ 03 июня 2012

Хотя это не обязательно для покрытия, я создал этот метод, чтобы убедиться, что служебный класс хорошо определен, а также немного охват.

/**
 * 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

18 голосов
/ 27 января 2012

Я сделал приватным конструктор моего класса статических служебных функций, чтобы удовлетворить CheckStyle. Но, как и оригинальный постер, я заставил Кобертуру пожаловаться на тест. Сначала я попробовал этот подход, но это не влияет на отчет о покрытии, потому что конструктор фактически никогда не выполняется. Таким образом, на самом деле все эти тесты заключаются в том, что конструктор остается закрытым - и это делается избыточным из-за проверки доступности в последующем тесте.

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

Я согласился с предложением Джавида Джаме и использовал рефлексию, но добавил утверждения, чтобы поймать кого-нибудь, кто вмешивается в тестируемый класс (и назвал этот тест, чтобы обозначить Высокие уровни зла).

@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% охвата метода.

9 голосов
/ 06 февраля 2015

С Java 8 , можно найти другое решение.

Я предполагаю, что вы просто хотите создать служебный класс с несколькими публичными статическими методами. Если вы можете использовать Java 8, тогда вы можете использовать interface.

package com.XXX;

public interface Foo {

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

Нет конструктора и нет жалоб от Кобертуры. Теперь вам нужно протестировать только те строки, которые вам действительно интересны.

5 голосов
/ 01 февраля 2016

Более новые версии Cobertura имеют встроенную поддержку игнорирования тривиальных геттеров / сеттеров / конструкторов:

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

Игнорировать Тривиальный

Игнорировать тривиально позволяет исключать конструкторы / методы, которые содержат одну строку кода. Некоторые примеры включают в себя вызов только суперконструктора, методов получения / установки и т. Д. Чтобы включить тривиальный аргумент игнорирования, добавьте следующее:

<cobertura-instrument ignoreTrivial="true" />

или в сборке Gradle:

cobertura {
    coverageIgnoreTrivial = true
}
5 голосов
/ 10 сентября 2011

Причиной тестирования кода, который ничего не делает, является достижение 100% покрытия кода и уведомление об уменьшении покрытия кода.В противном случае всегда можно подумать: эй, у меня больше нет 100% покрытия кода, но это ВЕРОЯТНО из-за моих личных конструкторов.Это позволяет легко обнаружить непроверенные методы, не проверяя, что это был только частный конструктор.По мере роста вашей кодовой базы вы на самом деле почувствуете приятное теплое чувство, глядя на 100% вместо 99%.

IMO, лучше использовать рефлексию здесь, так как в противном случае вам придется либо получить лучший инструмент покрытия кода, который игнорируетэти конструкторы или как-то сказать инструменту покрытия кода игнорировать метод (возможно, Annotation или файл конфигурации), потому что тогда вы застряли бы с конкретным инструментом покрытия кода.

В идеальном мире все инструменты покрытия кода будутигнорировать приватные конструкторы, которые принадлежат к конечному классу, потому что конструктор существует как мера "безопасности" и ничего больше :)Я бы использовал этот код:

    @Test
    public void callPrivateConstructorsForCodeCoverage() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
    {
        Class&#060;?&#062;[] classesToConstruct = {Foo.class};
        for(Class&#060;?&#062; clazz : classesToConstruct)
        {
            Constructor&#060;?&#062; constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            assertNotNull(constructor.newInstance());
        }
    }
А затем просто добавляйте классы в массив по мере продвижения.
4 голосов
/ 15 июня 2016

Не. Какой смысл тестировать пустой конструктор? Поскольку в cobertura 2.0 есть возможность игнорировать такие тривиальные случаи (вместе с установщиками / получателями), вы можете включить ее в maven, добавив раздел конфигурации в плагин cobertura maven:

<configuration>
  <instrumentation>
    <ignoreTrivial>true</ignoreTrivial>                 
  </instrumentation>
</configuration>

В качестве альтернативы вы можете использовать Аннотации покрытия : @CoverageIgnore.

3 голосов
/ 27 января 2012

Наконец, есть решение!

public enum Foo {;
  public static int bar() {
    return 1;
  }
}
1 голос
/ 27 июля 2018

ClassUnderTest testClass = Whitebox.invokeConstructor (ClassUnderTest.class);

...