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


110

これはコードです:

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


シングルトンパターンを適用しようとしているように見えます。もしそうなら、あなたはdp4j.com(それはまさにそれをします)が好きかもしれません
simpatico

「意図的に空」を例外のスローに置き換えないでください。その場合、特定の例外を特定のメッセージで期待するテストを作成できますか?これがやりすぎかどうかは不明
Ewoks '20

回答:


85

さて、あなたは潜在的に反射などを使用できる方法があります-しかし、それは本当に価値がありますか?これは決して呼ばれるべきではないコンストラクタですよね?

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

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


18
私は...という理由だけで、この特定のコンストラクタのプロジェクト全体では「ややカバレッジを削減」したくない
yegor256

36
@Vincenzo:IMOでは、単純な数値に高すぎる値を設定しています。カバレッジはテストの指標です。ツールの奴隷にならないでください。カバレッジのポイントは、ある程度の自信を与え、追加のテストの領域を提案することです。それ以外の場合は未使用のコンストラクターを人工的に呼び出すことは、これらの点のいずれにも役立ちません。
Jon Skeet、2010

19
@JonSkeet:私は「ツールの奴隷にならないでください」に完全に同意しますが、すべてのプロジェクトのすべての「欠陥の数」を覚えておくのはいい匂いがしません。7/9の結果がプログラマーではなくCoberturaの制限であることを確認するにはどうすればよいですか?新しいプログラマーはクラスごとにチェックするために、すべての失敗(大規模なプロジェクトでは多くの場合)を入力する必要があります。
エドゥアルドコスタ

5
これは質問の答えにはなりません。ちなみに、一部のマネージャーはカバレッジ数を調べています。彼らは理由を気にしません。彼らは、85%が75%より優れていることを知っています。
ACV 2016年

2
他の方法ではアクセスできないコードをテストするための実用的な使用例は、100%のテストカバレッジを達成することです。これにより、そのクラスを再度見る必要がなくなります。カバレッジが95%で止まっている場合、多くの開発者は、この問題に何度も何度も遭遇するために、その理由を理解しようと試みる可能性があります。
thisismydesign 2017

140

私はジョン・スキートに完全に同意しません。カバレッジを提供し、カバレッジレポートのノイズを排除するための簡単な勝利を手に入れることができるなら、そうすべきだと思います。コンストラクターを無視するようにカバレッジツールに指示するか、理想主義を脇に置いて次のテストを記述し、それで完了します。

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

25
ただし、テストスイートにノイズ追加することで、カバレッジレポートのノイズを排除しています。私は「観念論を脇に置いて」この文を終わらせただろう。:)
クリストファー・オー

11
このテストに何らかの意味を持たせるには、おそらく、コンストラクターのアクセスレベルが想定どおりであることも主張する必要があります。
ジェレミー

邪悪な反射とジェレミーのアイデアに加えて、「testIfConstructorIsPrivateWithoutRaisingExceptions」のような意味のある名前を追加すると、これは「THE」の答えだと思います。
エドゥアルドコスタ

1
これは構文的に正しくありませんか?なにconstructorConstructorパラメータ化されるべきではなく、生の型ではありませんか?
アダムパーキン2013年

2
これは間違ってconstructor.isAccessible()います。たとえパブリックコンストラクタであっても、常にfalseを返します。使用する必要がありますassertTrue(Modifier.isPrivate(constructor.getModifiers()));
timomeinen 2013年

78

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

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


11
+1これは、ツールをだますことなく問題を解決するだけでなく、ユーティリティクラスをセットアップするコーディング標準を完全にテストします。場合によっては、プライベートコンストラクターに戻るときに使用するModifier.isPrivateようisAccessibleに、アクセシビリティテストを変更する必要がtrueありました(ライブラリの干渉を模倣していますか?)。
David Harkness

4
これを本当にJUnitのAssertクラスに追加したいのですが、あなたの仕事を信用したくありません。とても良いと思います。Assert.utilityClassWellDefined()JUnit 4.12+に搭載するのは素晴らしいことです。プルリクエストを検討しましたか?
ビジョナリーソフトウェアソリューション

setAccessible()コンストラクターをアクセス可能にするためにを使用すると、Sonarのコードカバレッジツールに問題が発生することに注意してください(これを行うと、クラスがSonarのコードカバレッジレポートから消えます)。
アダムパーキン2013年

おかげで、私はアクセス可能なフラグをリセットします。おそらくそれはソナー自体のバグですか?
Archimedes Trajano 2013年

私のソナーレポートでバティックmavenプラグインのカバレッジを確認しましたが、正しくカバーされているようです。 site.trajano.net/batik-maven-plugin/cobertura/index.html
Archimedes Trajano 14

19

CheckStyleを満たすために、静的ユーティリティ関数のクラスのコンストラクタをprivateにしました。しかし、元のポスターのように、私はCoberturaにテストについて文句を言っていました。最初はこの方法を試しましたが、コンストラクタが実際に実行されることはないため、カバレッジレポートには影響しません。したがって、実際にこのすべてのテストは、コンストラクターがプライベートのままであるかどうかです。これは、後続のテストのアクセシビリティチェックによって冗長になります。

@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%であたたかいファジー感が好きだと認めなければなりません。


やりすぎかもしれませんが、Unitilsなどにあった場合は、私が使用します
Stewart


最初の例は機能しません-IllegalAccesExceptionはコンストラクターが呼び出されないことを意味するため、カバレッジは記録されません。
トムマッキンタイア

IMO、最初のコードスニペットのソリューションは、この説明の中で最もクリーンで最も単純なものです。ただ並べるfail(...)必要はありません。
ピョートルヴィッチェン2017年

9

Java 8、他の解決策を見つけることが可能です。

いくつかのパブリック静的メソッドを含むユーティリティクラスを単に作成したいと思います。Java 8を使用できる場合は、interface代わりに使用できます。

package com.XXX;

public interface Foo {

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

Coberturaからのコンストラクターや文句はありません。ここで、本当に気になる行のみをテストする必要があります。


1
ただし、残念ながら、インターフェイスを「最終」として宣言して、サブクラス化を防ぐことはできません。それ以外の場合は、これが最善の方法です。
マイケルベリー

5

何もしないコードをテストする背後にある理由は、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

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

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

些細なことを無視する

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

<cobertura-instrument ignoreTrivial="true" />

またはGradleビルドで:

cobertura {
    coverageIgnoreTrivial = true
}

4

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

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

別の方法としては、使用することができますカバレッジ注釈を@CoverageIgnore


3

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

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

質問に投稿されたクラスをテストすることはどうですか?プライベートコンストラクターを持つすべてのクラスをenumに変換できるとか、そうしたいと思ってはいけません。
Jon Skeet

@JonSkeet問題のクラスに参加できます。そして、静的メソッドの束しかないユーティリティクラスのほとんど。それ以外の場合、唯一のプライベートコンストラクターを持つクラスは意味がありません。
kan

1
プライベートコンストラクタを持つクラスがあり、もちろんそれはカバレッジを取得するのは簡単ですが、公共の静的メソッドからインスタンス化します。しかし、基本的に私はEnum<E>本当に列挙型になるクラスに拡張することを望みます...それは意図をよりよく明らかにすると私は信じています。
Jon Skeet

4
うわー、私はかなり任意の数よりも意味のあるコードを絶対に好みます。(カバレッジは品質を保証するものではなく、すべてのケースで100%のカバレッジが実現可能ではありません。テストは、コードを最高の状態でガイドする必要があります-奇妙な意図の崖を越えてそれを操縦しないでください。)
Jon Skeet

1
@Kan:ダミーの呼び出しをコンストラクターに追加して、ツールをブラフすることは意図されていません。プロジェクトの健全性を判断するために単一の指標に依存している人は、すでに破壊への道を進んでいます。
Apoorv Khurasia

1

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


1

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

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

  // real ops
}

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


私はかなりこれをします。廉価で安く、ダーティで安く、効果的。
pholser

Sonarでは、これにより実際にクラスがコードカバレッジによって完全に見落とされます。
Adam Parkin 2013年

1

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


質問された内容に正確に答えるので、これは正解だったはずです。
Chakian

0

Coberturaは、実行対象外のコードを「カバーされていない」とマークする場合がありますが、何も問題はありません。99%代わりにカバレッジに関心があるのはなぜ100%ですか?

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


0

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

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

1の場合、すべての初期化をファクトリーメソッドを介して実行したいことは明らかです。そのような場合、テストはコンストラクターの副作用をテストできるはずです。これは、通常のプライベートメソッドテストのカテゴリに分類されます。メソッドを小さくして、限られた数の決定的なこと(理想的には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>

このようなコンストラクターは例外をスローすることを意図していないため、故意に.*以下)を省略しました(何も実行することを意図していません)。

もちろん、非ユーティリティクラスの空のコンストラクタが必要になる3番目のケースもあります。そのような場合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
@Test
public void testTestPrivateConstructor() {
    Constructor<Test> cnt;
    try {
        cnt = Test.class.getDeclaredConstructor();
        cnt.setAccessible(true);

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

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


この構造がカバレッジに役立つ理由を説明するとよいでしょう。
Markus

真の、そして第二に:なぜあなたのテストで例外をキャッチするのですか?スローされる例外は、実際にテストを失敗させるはずです。
ジョルディ2013年

0

以下は、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
}

プライベートコンストラクターが手動で記述されている場合、constructor.setAccessible(true)は機能するはずですが、Lombokアノテーションを使用すると、強制されるため機能しません。Constructor.newInstance()は実際にコンストラクターが呼び出されたことをテストし、これによりココンストラクター自体のカバレッジが完了します。assertThrowsを使用することで、テストが失敗することを防ぎ、例外を管理できます。これは、まさに予想されるエラーだからです。これは回避策であり、「ラインカバレッジ」対「機能性/動作カバレッジ」の概念を理解していませんが、このテストで感覚を見つけることができます。実際、ユーティリティクラスには、reflactionを介して呼び出された場合に例外を正しくスローするプライベートコンストラクターが実際にあることが確実です。お役に立てれば。


こんにちは@ShanteshwarInde。どうもありがとう。私の入力は編集され、あなたの提案に従って完了しました。よろしく。
Riccardo Solimena

0

2019年の私の推奨オプション:ロンボクを使用します。

具体的には、@UtilityClassアノテーション。(残念ながら、執筆時点では「実験的」にすぎませんが、問題なく機能し、前向きな見通しを持っているため、間もなく安定版にアップグレードされます。)

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


-2

できません。

静的メソッドのみを含むことを目的としたクラスのインスタンス化を防ぐために、プライベートコンストラクターを作成しているようです。このコンストラクタのカバレッジを取得しようとするのではなく(クラスをインスタンス化する必要があります)、それを取り除き、開発者がクラスにインスタンスメソッドを追加しないように信頼する必要があります。


3
それは不正解です。上記のように、リフレクションを介してインスタンス化できます。
テオセラピー

これは悪いことです。デフォルトのパブリックコンストラクターが表示されないようにしてください。プライベートコンストラクターを追加して、呼び出されないようにする必要があります。
ローベン2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.