これはコードです:
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に呼び出されないことを理解させるためにクラスに追加できる注釈または類似のものがある場合は、それを実行します。人為的にカバレッジを追加するためにフープを通過する価値はないと思います。
編集:それを行う方法がない場合は、わずかに減少したカバレッジでライブします。カバレッジはyoに役立つものであることを忘れないでください-あなたはツールを担当するべきであり、その逆ではありません。
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
些細なことを無視する
些細なことを無視すると、1行のコードを含むコンストラクター/メソッドを除外できます。いくつかの例には、スーパーコンストラクターの呼び出し、ゲッター/セッターメソッドなどが含まれます。無視する自明な引数を含めるには、次を追加します。
<cobertura-instrument ignoreTrivial="true" />
またはGradleビルドで:
cobertura {
coverageIgnoreTrivial = true
}
しないでください。空のコンストラクターをテストする意味は何ですか? cobertura 2.0以降では、このような些細なケースを(セッター/ゲッターと共に)無視するオプションがあります。構成セクションをcobertura mavenプラグインに追加することで、mavenで有効にできます。
<configuration>
<instrumentation>
<ignoreTrivial>true</ignoreTrivial>
</instrumentation>
</configuration>
または、 Coverage Annotations :@CoverageIgnore
。
最後に、解決策があります!
public enum Foo {;
public static int bar() {
return 1;
}
}
ClassUnderTest testClass = Whitebox.invokeConstructor(ClassUnderTest.class);
Coberturaについては知りませんが、Cloverを使用しており、パターンマッチングの除外を追加する手段があります。たとえば、Apache-commons-logging行を除外するパターンがあるので、それらはカバレッジでカウントされません。
別のオプションは、次のコードのような静的初期化子を作成することです
class YourClass {
private YourClass() {
}
static {
new YourClass();
}
// real ops
}
この方法では、プライベートコンストラクターはテスト済みと見なされ、実行時のオーバーヘッドは基本的に測定できません。 EclEmmaを使用して100%のカバレッジを得るためにこれを行いますが、すべてのカバレッジツールでうまくいく可能性があります。もちろん、このソリューションの欠点は、テスト目的でのみプロダクションコード(静的初期化子)を書くことです。
私の好みのオプション:ロンボクを使用します。
具体的には、 @UtilityClass
アノテーション 。 (執筆時点では残念ながら「実験的」のみですが、正常に機能し、肯定的な見通しを持っているため、すぐに安定版にアップグレードされる可能性があります。)
このアノテーションは、インスタンス化を防ぐためにプライベートコンストラクターを追加し、クラスをfinalにします。 lombok.addLombokGeneratedAnnotation = true
のlombok.config
と組み合わせると、テストカバレッジの計算時にほとんどすべてのテストフレームワークが自動生成コードを無視するため、ハックやリフレクションなしでその自動生成コードのカバレッジをバイパスできます。
Coberturaは、実行されることを意図していないコードを「カバーされていない」とマークする場合がありますが、それは何の問題もありません。なぜ99%
カバレッジの代わりに100%
?
ただし、技術的には、リフレクションを使用してそのコンストラクターを呼び出すことはできますが、私には非常に間違っています(この場合)。
@Test
public void testTestPrivateConstructor() {
Constructor<Test> cnt;
try {
cnt = Test.class.getDeclaredConstructor();
cnt.setAccessible(true);
cnt.newInstance();
} catch (Exception e) {
e.getMessage();
}
}
Test.Javaはソースファイルで、プライベートコンストラクターがあります
私があなたの質問の意図を推測するなら、私は言うでしょう:
1の場合、すべての初期化がファクトリメソッドを介して行われることは明らかです。そのような場合、テストはコンストラクターの副作用をテストできるはずです。これは、通常のプライベートメソッドテストのカテゴリに分類されるはずです。限られた数の確定的なこと(理想的には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コンストラクターを除外するように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>