source

사설 컨스트럭터에 테스트 적용 범위를 추가하려면 어떻게 해야 합니까?

manycodes 2023. 1. 29. 20:19
반응형

사설 컨스트럭터에 테스트 적용 범위를 추가하려면 어떻게 해야 합니까?

코드는 다음과 같습니다.

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는 이 클래스의 개인 컨스트럭터에는 코드 커버리지가 없다고 말합니다.이러한 개인 건설업체에 테스트 적용 범위를 추가하려면 어떻게 해야 합니까?

나는 존 스키트의 말에 전적으로 동의하지는 않는다.커버리지를 쉽게 획득할 수 있고 커버리지의 노이즈를 제거할 수 있다면 그렇게 해야 한다고 생각합니다.커버리지 툴에 컨스트럭터를 무시하도록 지시하거나 이상주의를 제쳐두고 다음 테스트를 작성하여 완료하십시오.

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

음, 여러분이 잠재적으로 성찰 등을 사용할 수 있는 방법들이 있습니다 - 하지만 정말 그럴 가치가 있을까요?이건 절대 불러서는 안 되는 건설업자죠?

Cobertura에게 그것이 호출되지 않는다는 것을 이해시키기 위해 클래스에 추가할 수 있는 주석이나 비슷한 것이 있다면 그렇게 하세요: 인위적으로 커버리지를 추가하는 것은 후프를 거칠 가치가 없다고 생각합니다.

편집: 방법이 없는 경우는, 커버리지가 약간 축소된 상태로 해 주세요.커버리지란 자신에게 유용한 것을 의미합니다.툴의 담당자는, 그 반대는 아니고, 고객입니다.

반드시 커버리지가 필요한 것은 아니지만 유틸리티 클래스가 잘 정의되어 있는지 확인하고 커버리지도 조금 하기 위해 이 방법을 만들었습니다.

/**
 * 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을 만족시키기 위해 내 클래스의 정적 유틸리티 함수 생성자를 비공개로 만들었습니다.하지만 원래 포스터와 마찬가지로 코베르투라가 시험에 대해 불평했다.처음에는 이 방법을 시도했지만 컨스트럭터는 실제로 실행되지 않기 때문에 커버리지 리포트에 영향을 주지 않습니다.따라서 이 모든 테스트는 컨스트럭터가 비공개로 유지되고 있는지 여부입니다.다음 테스트에서의 접근성 체크에 의해 용장화됩니다.

@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에서는 다른 솔루션을 찾을 수 있습니다.

퍼블릭 스태틱메서드가 거의 없는 유틸리티클래스를 만들고 싶다고 생각합니다.8을 할 수 8을 사용할 수 .interface★★★★★★ 。

package com.XXX;

public interface Foo {

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

Cobertura로부터의 불평도 건설자도 없다.이제 정말 마음에 드는 라인만 테스트하면 됩니다.

아무 것도 하지 않는 코드를 테스트하는 이유는 100% 코드 커버리지를 달성하고 코드 커버리지가 떨어졌을 때 주목하는 것입니다.그렇지 않으면 항상 이렇게 생각할 수 있다, 이봐, 난 더 이상 100% 코드 커버리지는 없지만, 그건 PROBAB야.LY는 내 개인 건설업자 덕분이다.이를 통해 프라이빗 컨스트럭터임을 확인할 필요 없이 테스트되지 않은 방법을 쉽게 찾을 수 있습니다.코드 베이스가 커짐에 따라, 99%가 아닌 100%를 보고 있으면, 기분 좋은 따뜻함을 느낄 수 있습니다.

IMO에서는 리플렉션(reflection)을 사용하는 것이 가장 좋습니다.그렇지 않으면 이러한 컨스트럭터를 무시하는 더 나은 코드 커버리지 툴을 얻거나 코드 커버리지 툴(아마 주석이나 설정 파일)을 무시하도록 코드 커버리지 툴에 지시해야 합니다.

완벽한 세계에서는 모든 코드 적용 범위 툴은 최종 클래스에 속하는 사설 컨스트럭터를 무시합니다. 왜냐하면 컨스트럭터는 "보안" 수단으로서 존재하기 때문입니다.
다음 코드를 사용합니다.

    @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());
        }
    }
And then just add classes to the array as you go.

Cobertura의 새로운 버전에는 사소한 getter/setter/constructor를 무시하는 기능이 포함되어 있습니다.

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

사소한 무시

Ignore trivial을 사용하면 코드 한 줄이 포함된 생성자/메서드를 제외할 수 있습니다.예를 들어 슈퍼 컨스트럭터 전용 콜, getter/setter 메서드 등이 있습니다.ignore trivial 인수를 포함하려면 다음을 추가합니다.

<cobertura-instrument ignoreTrivial="true" />

또는 Gradle 빌드:

cobertura {
    coverageIgnoreTrivial = true
}

하지 마 빈 시공자를 시험해 봐야 무슨 소용이야?cobertura 2.0에는 이러한 사소한 케이스(setters/getters와 함께)를 무시하는 옵션이 있으므로 cobertura maven 플러그인에 설정 섹션을 추가하여 maven에서 활성화할 수 있습니다.

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

또는 커버리지 주석을 사용할 수 있습니다.@CoverageIgnore.

2019+에서 선호하는 옵션:롬복으로.

구체적으로는 주석입니다.(안타깝게도 집필 당시에는 '실험적'이었지만 기능만 양호하고 전망도 긍정적이기 때문에 곧 안정적으로 업그레이드 될 것 같습니다.)

이 주석은 인스턴스화를 방지하기 위해 개인 생성자를 추가하고 클래스를 최종화합니다.「 」와 lombok.addLombokGeneratedAnnotation = truelombok.config테스트 범위 계산 시 거의 모든 테스트 프레임워크가 자동 생성된 코드를 무시하므로 해킹이나 반사 없이 자동 생성된 코드의 커버리지를 우회할 수 있습니다.

드디어 해결책이 있다!

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

Lombok 주석 @UtilityClass로 작성된 클래스에서 프라이빗 컨스트럭터를 자동으로 추가하는 방법은 다음과 같습니다.

@Test
public void testConstructorIsPrivate() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
    Constructor<YOUR_CLASS_NAME> constructor = YOUR_CLASS_NAME.class.getDeclaredConstructor();
    assertTrue(Modifier.isPrivate(constructor.getModifiers())); //this tests that the constructor is private
    constructor.setAccessible(true);
    assertThrows(InvocationTargetException.class, () -> {
        constructor.newInstance();
    }); //this add the full coverage on private constructor
}

컨스트럭터지만.setAccessible(true)은 프라이빗 컨스트럭터가 수동으로 작성되어 있고, 롬복 주석은 강제로 작성되었기 때문에 동작하지 않습니다.Constructor.newInstance()는 실제로 컨스트럭터가 호출되어 코스트레이터 자체의 커버리지를 테스트합니다.주장과 함께테스트가 실패하여 사용자가 예외를 관리하지 않도록 합니다.이는 예상한 오류와 일치하기 때문입니다.이것은 회피책이며, 「회선 커버리지」와 「기능/동작 커버리지」의 개념이 마음에 들지 않습니다만, 이 테스트에서는 의미를 찾을 수 있습니다.실제로 Utility Class에는 리플랙션을 통해 호출될 때 예외를 올바르게 발생시키는 Private Constructor가 있습니다.이게 도움이 됐으면 좋겠다.

Cobertura에 대해서는 잘 모르지만, Clover를 사용하고 있기 때문에 패턴 매칭 제외를 추가할 수 있습니다.예를 들어 apache-commons-logging 행을 제외하는 패턴이 있으므로 해당 행은 커버리지에 카운트되지 않습니다.

또 다른 옵션은 다음 코드와 유사한 정적 이니셜라이저를 작성하는 것입니다.

class YourClass {
  private YourClass() {
  }
  static {
     new YourClass();
  }

  // real ops
}

이렇게 하면 프라이빗 컨스트럭터가 테스트된 것으로 간주되며 기본적으로 런타임 오버헤드를 측정할 수 없습니다.Ecl Emma를 사용하여 100% 커버리지를 얻기 위해 이 작업을 수행하지만, 모든 커버리지를 이용할 수 있습니다.물론 이 솔루션의 단점은 테스트 목적으로만 프로덕션 코드(스태틱 이니셜라이저)를 쓴다는 것입니다.

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

때때로 Cobertura는 실행이 의도되지 않은 코드를 "not covered"로 표시하는데, 그것은 전혀 문제가 되지 않습니다.은 '우리'가 있는 것에 염려하고 있습니까?99%100%

그러나 엄밀히 말하면, 당신은 여전히 그 컨스트럭터를 반영으로 호출할 수 있지만, (이 경우) 매우 잘못된 것 같습니다.

당신의 질문의 의도를 추측한다면, 다음과 같이 말할 것입니다.

  1. 실제 작업을 수행하는 프라이빗 컨스트럭터에 대한 합리적인 체크가 필요합니다.
  2. util 클래스에 대해 빈 생성자를 제외하기 위해 clover를 사용하려고 합니다.

1의 경우 모든 초기화가 공장 방법을 통해 수행되어야 합니다.이러한 경우 검정에서 생성자의 부작용을 검정할 수 있어야 합니다.이는 일반 개인 방법 테스트의 범주에 속해야 합니다.한정된 수의 결정적인 일(이상적으로는 한 가지 일, 한 가지 일)만 수행하도록 방법을 작게 만든 다음 이에 의존하는 방법을 테스트합니다.

를 들어private 필드를 설정한 a로로 합니다.5그럼 테스트(또는 테스트해야 한다)할 수 있습니다.

@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 클래스의 이름 패턴이 설정된 경우 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>

.*)왜냐하면 이러한 컨스트럭터는 예외를 두는 것을 의도하지 않기 때문입니다(그것들은 아무것도 의도하지 않습니다).

물론 유틸리티가 아닌 클래스의 빈 컨스트럭터를 필요로 하는 세 번째 경우도 있습니다.때는 꼭 '아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아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>

있는 컨스트럭터 하여 reg-ex를 삭제할 수 .Util컨스트럭터의 이 클래스프로젝트의 다른 으로 테스트되고 있는지 .이 경우 컨스트럭터의 부작용이 클래스/프로젝트의 다른 메서드에 의해 테스트되고 있는지 수동으로 확인해야 합니다.

<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는 개인 컨스트럭터가 있는 소스 파일입니다.

그럴수는 없어요.

정적 메서드만 포함하는 클래스의 인스턴스화를 방지하기 위해 개인 생성자를 만들고 있는 것 같습니다.이 컨스트럭터의 커버리지(클래스를 인스턴스화해야 함)를 취득하려고 하지 말고 이를 삭제하고 개발자가 클래스에 인스턴스 메서드를 추가하지 않도록 신뢰해야 합니다.

언급URL : https://stackoverflow.com/questions/4520216/how-to-add-test-coverage-to-a-private-constructor

반응형