Programming

개인 생성자에 테스트 커버리지를 추가하는 방법은 무엇입니까?PHP : stdClass 객체 계산

procodes 2020. 8. 10. 08:25
반응형

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


다음은 코드입니다.

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을 사용하면 다른 솔루션을 찾을 수 있습니다.

I assume that you simply want to create utility class with few public static methods. If you can use Java 8, then you can use interface instead.

package com.XXX;

public interface Foo {

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

There is no constructor and no complain from Cobertura. Now you need to test only the lines you really care about.


The reasoning behind testing code that doesn't do anything is to achieve 100% code coverage and to notice when the code coverage drops. Otherwise one could always think, hey I don't have 100% code coverage anymore but it's PROBABLY because of my private constructors. This makes it easy to spot untested methods without having to check that it just was a private constructor. As your codebase grows you'll actually feel a nice warm feeling looking at 100% instead of 99%.

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 allows the ability to exclude constructors/methods that contain one line of code. Some examples include a call to a super constrctor only, getter/setter methods, etc. To include the ignore trivial argument add the following:

<cobertura-instrument ignoreTrivial="true" />

or in a Gradle build:

cobertura {
    coverageIgnoreTrivial = true
}

Don't. What's the point in testing an empty constructor? Since cobertura 2.0 there is an option to ignore such trivial cases (together with setters/getters), you can enable it in maven by adding configuration section to cobertura maven plugin:

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

Alternatively you can use Coverage Annotations: @CoverageIgnore.


Finally, there is solution!

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

I don't know about Cobertura but I use Clover and it has a means of adding pattern-matching exclusions. For example, I have patterns that exclude apache-commons-logging lines so they are not counted in the coverage.


Another option is to create a static initializer similar to the following code

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

  // real ops
}

This way the private constructor is considered tested, and the runtime overhead is basically not measurable. I do this to get 100% coverage using EclEmma, but likely it works out for every coverage tool. The drawback with this solution, of course, is that you write production code (the static initializer) just for testing purposes.


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


My preferred option in 2019: Use lombok.

Specifically, the @UtilityClass annotation. (Sadly only "experimental" at the time of writing, but it functions just fine and has a positive outlook, so likely to soon be upgraded to stable.)

This annotation will add the private constructor to prevent instantiation and will make the class final. When combined with lombok.addLombokGeneratedAnnotation = true in lombok.config, pretty much all testing frameworks will ignore the auto-generated code when calculating test coverage, allowing you to bypass coverage of that auto-generated code with no hacks or reflection.


Sometimes Cobertura marks code not intended to be executed as 'not covered', there's nothing wrong with that. Why are you concerned with having 99% coverage instead of 100%?

Technically, though, you can still invoke that constructor with reflection, but it sounds very wrong to me (in this case).


If I were to guess the intent of your question I'd say:

  1. You want reasonable checks for private constructors that do actual work, and
  2. You want clover to exclude empty constructors for util classes.

For 1, it is obvious that you want all initialisation to be done via factory methods. In such cases, your tests should be able to test the side effects of the constructor. This should fall under the category of normal private method testing. Make the methods smaller so that they only do a limited number of determinate things (ideally, just one thing and one thing well) and then test the methods that rely on them.

For example, if my [private] constructor sets up my class's instance fields a to 5. Then I can (or rather must) test it:

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

For 2, you can configure clover to exclude Util constructors if you have a set naming pattern for Util classes. E.g., in my own project I use something like this (because we follow the convention that names for all Util classes should end with Util):

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
</clover-setup>

I have deliberately left out a .* following ) because such constructors are not meant to throw exceptions (they are not meant to do anything).

There of course can be a third case where you may want to have an empty constructor for a non-utility class. In such cases, I would recommend that you put a methodContext with the exact signature of the constructor.

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

If you have many such exceptional classes then you can choose to modify the generalized private constructor reg-ex I suggested and remove Util from it. In this case, you will have to manually make sure that your constructor's side effects are still tested and covered by other methods in your class/project.

<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 is your source file,which is having your private constructor


You can't.

You're apparently creating the private constructor to prevent instantiation of a class that is intended to contain only static methods. Rather than trying to get coverage of this constructor (which would require that the class be instantiated), you should get rid of it and trust your developers not to add instance methods to the class.


The following worked to me on a class created with Lombok annotation @UtilityClass, that automatically add a private constructor.

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

Although constructor.setAccessible(true) should works when private constructor has been written manually, with Lombok annotation doesn't work, since it force it. Constructor.newInstance() actually tests that the constructor is invoked and this complete the coverage of the costructor itself. With the assertThrows you prevent that the test fails and you managed the exception since is exactly the error that you expect. Although this is a workaround and I don't appreciate the concept of "line coverage" vs "functionality/behavior coverage" we can find a sense on this test. In fact you are sure that the Utility Class has actually a private Constructor that correctly throws an exception when invoked also via reflaction. Hope this helps.

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

PHP : stdClass 객체 계산


count ($ obj) 함수를 실행할 때 올바른 숫자를 반환하지 않는 json_decode에서 만든 stdClass 객체가 있습니다. 객체에는 30 개의 속성이 있지만 count () 함수의 반환 값은 1입니다.

어떤 아이디어?

아래는 개체 중 하나의 예입니다. (트위터에서 일일 트렌드 정보를 요청하고 있습니다.) 이 객체에 둘 이상의 속성이있는 경우 count ($ obj)는 1이됩니다.

[trends] => stdClass Object
    (
        [2009-08-21 11:05] => Array
            (
                [0] => stdClass Object
                    (
                        [query] => "Follow Friday"
                        [name] => Follow Friday
                    )

                [1] => stdClass Object
                    (
                        [query] => "Inglourious Basterds" OR "Inglorious Basterds"
                        [name] => Inglourious Basterds
                    )

                [2] => stdClass Object
                    (
                        [query] => Inglourious
                        [name] => Inglourious
                    )

                [3] => stdClass Object
                    (
                        [query] => #songsincode
                        [name] => #songsincode
                    )

                [4] => stdClass Object
                    (
                        [query] => #shoutout
                        [name] => #shoutout
                    )

                [5] => stdClass Object
                    (
                        [query] => "District 9"
                        [name] => District 9
                    )

                [6] => stdClass Object
                    (
                        [query] => #howmanypeople
                        [name] => #howmanypeople
                    )

                [7] => stdClass Object
                    (
                        [query] => Ashes OR #ashes
                        [name] => Ashes
                    )

                [8] => stdClass Object
                    (
                        [query] => #youtubefail
                        [name] => #youtubefail
                    )

                [9] => stdClass Object
                    (
                        [query] => TGIF
                        [name] => TGIF
                    )

                [10] => stdClass Object
                    (
                        [query] => #wish09
                        [name] => #wish09
                    )

                [11] => stdClass Object
                    (
                        [query] => #watch
                        [name] => #watch
                    )

                [12] => stdClass Object
                    (
                        [query] => Avatar
                        [name] => Avatar
                    )

                [13] => stdClass Object
                    (
                        [query] => Ramadhan
                        [name] => Ramadhan
                    )

                [14] => stdClass Object
                    (
                        [query] => Goodnight
                        [name] => Goodnight
                    )

                [15] => stdClass Object
                    (
                        [query] => iPhone
                        [name] => iPhone
                    )

                [16] => stdClass Object
                    (
                        [query] => #iranelection
                        [name] => #iranelection
                    )

                [17] => stdClass Object
                    (
                        [query] => Apple
                        [name] => Apple
                    )

                [18] => stdClass Object
                    (
                        [query] => "Usain Bolt"
                        [name] => Usain Bolt
                    )

                [19] => stdClass Object
                    (
                        [query] => H1N1
                        [name] => H1N1
                    )

            )
     )

문제는 count가 개체의 속성이 아니라 배열의 인덱스를 계산하기위한 것입니다 (Countable 인터페이스를 구현하는 사용자 지정 개체가 아닌 경우). 아래와 같이 객체를 배열로 캐스팅하고 도움이되는지 확인하십시오.

$total = count((array)$obj);

단순히 객체를 배열로 캐스팅하는 것이 항상 작동하는 것은 아니지만 간단한 stdClass 객체이므로 여기서 작업을 수행해야합니다.


카운트 기능은

  1. 배열
  2. Objects that are derived from classes that implement the countable interface

A stdClass is neither of these. The easier/quickest way to accomplish what you're after is

$count = count(get_object_vars($some_std_class_object));

This uses PHP's get_object_vars function, which will return the properties of an object as an array. You can then use this array with PHP's count function.


The object doesn't have 30 properties. It has one, which is an array that has 30 elements. You need the number of elements in that array.


There is nothing wrong with count() here, "trends" is the only key that is being counted in this case, you can try doing:

count($obj->trends);

Or:

count($obj->trends['2009-08-21 11:05']);

Or maybe even doing:

count($obj, COUNT_RECURSIVE);

Just use this

$i=0;
foreach ($object as $key =>$value)
{
$i++;
}

the variable $i is number of keys.


count() function works with array. But if you want to count object's length then you can use this method.

$total = $obj->length;

참고URL : https://stackoverflow.com/questions/1314745/php-count-a-stdclass-object

반응형