[java] 개인 생성자에 테스트 커버리지를 추가하는 방법은 무엇입니까?

다음은 코드입니다.

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가 호출되지 않는다는 것을 이해하도록 클래스에 추가 할 수있는 주석 또는 이와 유사한 것이있는 경우 그렇게하십시오. 인위적으로 커버리지를 추가하기 위해 후프를 거칠 가치가 없다고 생각합니다.

편집 : 그것을 할 방법이 없다면 약간 감소 된 범위로 생활하십시오. 커버리지는 당신에게 유용한 것임을 기억하세요. 당신은 도구를 담당해야합니다.


답변

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


답변

반드시 커버리지를위한 것은 아니지만 유틸리티 클래스가 잘 정의되어 있는지 확인하고 약간의 커버리지를 수행하기 위해이 메서드를 만들었습니다.

/**
 * 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을 만족시키기 위해 내 정적 유틸리티 함수 클래스의 생성자를 private으로 만들었습니다. 그러나 원래 포스터와 마찬가지로 Cobertura가 테스트에 대해 불평했습니다. 처음에는이 방법을 시도했지만 생성자가 실제로 실행되지 않기 때문에 커버리지 보고서에 영향을 미치지 않습니다. 따라서 실제로이 모든 테스트는 생성자가 비공개로 유지되는 경우입니다.이 테스트는 후속 테스트에서 접근성 검사에 의해 중복됩니다.

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

나는 Javid Jamae의 제안을 따르고 반성을 사용했지만 테스트중인 클래스를 망쳐 놓은 사람을 잡기 위해 단언을 추가했습니다 (그리고 테스트 이름은 High Levels Of Evil을 나타냄).

@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 % 메서드 커버리지의 따뜻한 퍼지 느낌이 좋아요.


답변

Java 8을 사용하면 다른 솔루션을 찾을 수 있습니다.

공개 정적 메서드가 거의없는 유틸리티 클래스를 간단히 만들고 싶다고 가정합니다. Java 8을 사용할 수 있으면 interface대신 사용할 수 있습니다 .

package com.XXX;

public interface Foo {

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

Cobertura에서는 생성자도없고 불평도 없습니다. 이제 정말 관심있는 라인 만 테스트하면됩니다.


답변

아무것도하지 않는 코드를 테스트하는 이유는 100 % 코드 커버리지를 달성하고 코드 커버리지가 떨어질 때를 알아 차리는 것입니다. 그렇지 않으면 항상 생각할 수 있습니다. 더 이상 100 % 코드 커버리지가 없지만 개인 생성자 때문에 가능성이 있습니다. 이렇게하면 개인 생성자인지 확인하지 않고도 테스트되지 않은 메서드를 쉽게 찾을 수 있습니다. 코드베이스가 커짐에 따라 실제로 99 %가 아닌 100 %를 보면 따뜻한 느낌이들 것입니다.

IMO에서는 리플렉션을 사용하는 것이 가장 좋습니다. 특정 코드 커버리지 도구로.

완벽한 세상에서 모든 코드 커버리지 도구는 최종 클래스에 속하는 개인 생성자를 무시할 것입니다. 생성자가 “보안”측정 값으로 다른 것은 없기 때문입니다.)
이 코드를 사용합니다.

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

그런 다음 진행하면서 배열에 클래스를 추가하십시오.


답변

최신 버전의 Cobertura에는 사소한 게터 / 세터 / 생성자를 무시할 수있는 내장 지원 기능이 있습니다.

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

사소한 것 무시

Ignore trivial은 코드 한 줄을 포함하는 생성자 / 메소드를 제외하는 기능을 허용합니다. 몇 가지 예에는 super constrctor에 대한 호출, getter / setter 메소드 등이 포함됩니다. ignore 간단한 인수를 포함하려면 다음을 추가하십시오.

<cobertura-instrument ignoreTrivial="true" />

또는 Gradle 빌드에서 :

cobertura {
    coverageIgnoreTrivial = true
}