web-dev-qa-db-ja.com

プライベートコンストラクターにテストカバレッジを追加する方法は?

これはコードです:

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によれば、クラスのプライベートコンストラクターのコードカバレッジはゼロです。このようなプライベートコンストラクターにテストカバレッジを追加するにはどうすればよいですか?

103
yegor256

さて、潜在的にリフレクションなどを使用できる方法があります-しかし、それは本当に価値がありますか?これは決して呼び出されないである必要があるコンストラクターです?

Coberturaに呼び出されないことを理解させるためにクラスに追加できる注釈または類似のものがある場合は、それを実行します。人為的にカバレッジを追加するためにフープを通過する価値はないと思います。

編集:それを行う方法がない場合は、わずかに減少したカバレッジでライブします。カバレッジはyoに役立つものであることを忘れないでください-あなたはツールを担当するべきであり、その逆ではありません。

77
Jon Skeet

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

必ずしもカバーする必要はありませんが、ユーティリティクラスが適切に定義されていることを確認するためにこのメソッドを作成し、同様に少しカバーします。

/**
 * 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 に配置しました

74

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%の温かいあいまいな感じが好きだと認めざるを得ません。

18
Ben Hardy

Java 8を使用すると、他の解決策を見つけることができます。

パブリックな静的メソッドをほとんど持たないユーティリティクラスを作成したいだけだと思います。 Java 8)を使用できる場合は、代わりにinterfaceを使用できます。

package com.XXX;

public interface Foo {

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

コンストラクターもCoberturaからの苦情もありません。ここで、本当に気にする行だけをテストする必要があります。

9
Arnost Valicek

何もしないコードのテストの背後にある理由は、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());
        }
    }
5
jontejj

Coberturaの新しいバージョンには、些細なゲッター/セッター/コンストラクターを無視するためのサポートが組み込まれています:

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

些細なことを無視する

些細なことを無視すると、1行のコードを含むコンストラクター/メソッドを除外できます。いくつかの例には、スーパーコンストラクターの呼び出し、ゲッター/セッターメソッドなどが含まれます。無視する自明な引数を含めるには、次を追加します。

<cobertura-instrument ignoreTrivial="true" />

またはGradleビルドで:

cobertura {
    coverageIgnoreTrivial = true
}
5
Mike Buhot

しないでください。空のコンストラクターをテストする意味は何ですか? cobertura 2.0以降では、このような些細なケースを(セッター/ゲッターと共に)無視するオプションがあります。構成セクションをcobertura mavenプラグインに追加することで、mavenで有効にできます。

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

または、 Coverage Annotations@CoverageIgnore

4

最後に、解決策があります!

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

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

1
acpuma

Coberturaについては知りませんが、Cloverを使用しており、パターンマッチングの除外を追加する手段があります。たとえば、Apache-commons-logging行を除外するパターンがあるので、それらはカバレッジでカウントされません。

1
John Engelman

別のオプションは、次のコードのような静的初期化子を作成することです

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

  // real ops
}

この方法では、プライベートコンストラクターはテスト済みと見なされ、実行時のオーバーヘッドは基本的に測定できません。 EclEmmaを使用して100%のカバレッジを得るためにこれを行いますが、すべてのカバレッジツールでうまくいく可能性があります。もちろん、このソリューションの欠点は、テスト目的でのみプロダクションコード(静的初期化子)を書くことです。

1

私の好みのオプション:ロンボクを使用します。

具体的には、 @UtilityClassアノテーション 。 (執筆時点では残念ながら「実験的」のみですが、正常に機能し、肯定的な見通しを持っているため、すぐに安定版にアップグレードされる可能性があります。)

このアノテーションは、インスタンス化を防ぐためにプライベートコンストラクターを追加し、クラスをfinalにします。 lombok.addLombokGeneratedAnnotation = truelombok.configと組み合わせると、テストカバレッジの計算時にほとんどすべてのテストフレームワークが自動生成コードを無視するため、ハックやリフレクションなしでその自動生成コードのカバレッジをバイパスできます。

1
Michael Berry

Coberturaは、実行されることを意図していないコードを「カバーされていない」とマークする場合がありますが、それは何の問題もありません。なぜ99%カバレッジの代わりに100%

ただし、技術的には、リフレクションを使用してそのコンストラクターを呼び出すことはできますが、私には非常に間違っています(この場合)。

0
Nikita Rybak
@Test
public void testTestPrivateConstructor() {
    Constructor<Test> cnt;
    try {
        cnt = Test.class.getDeclaredConstructor();
        cnt.setAccessible(true);

        cnt.newInstance();
    } catch (Exception e) {
        e.getMessage();
    }
}

Test.Javaはソースファイルで、プライベートコンストラクターがあります

0
DPREDDY

私があなたの質問の意図を推測するなら、私は言うでしょう:

  1. 実際の作業を行うプライベートコンストラクターの妥当なチェックが必要であり、かつ
  2. Cloverで、utilクラスの空のコンストラクターを除外する必要があります。

1の場合、すべての初期化がファクトリメソッドを介して行われることは明らかです。そのような場合、テストはコンストラクターの副作用をテストできるはずです。これは、通常のプライベートメソッドテストのカテゴリに分類されるはずです。限られた数の確定的なこと(理想的には1つのことと1つのことだけ)を行うようにメソッドを小さくし、それらに依存するメソッドをテストします。

たとえば、[private]コンストラクターがクラスのインスタンスフィールドa5に設定する場合。その後、私はそれをテストすることができます(むしろ必要です):

@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コンストラクターを除外するようにcloverを構成できます。たとえば、自分のプロジェクトでは、次のようなものを使用します(すべての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>
0
Apoorv Khurasia