Dies ist der Code:
package com.XXX;
public final class Foo {
private Foo() {
// intentionally empty
}
public static int bar() {
return 1;
}
}
Dies ist der Test:
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
}
}
Funktioniert gut, die Klasse wird getestet. Cobertura sagt jedoch, dass der private Konstruktor der Klasse keine Codeabdeckung aufweist. Wie können wir einem solchen privaten Konstruktor eine Testabdeckung hinzufügen?
java
testing
code-coverage
yegor256
quelle
quelle
Antworten:
Nun, es gibt Möglichkeiten, wie Sie möglicherweise Reflexion usw. verwenden können - aber lohnt es sich wirklich? Dies ist ein Konstruktor, der niemals aufgerufen werden sollte , oder?
Wenn es eine Anmerkung oder ähnliches gibt, die Sie der Klasse hinzufügen können, damit Cobertura versteht, dass sie nicht aufgerufen wird, tun Sie Folgendes: Ich denke, es lohnt sich nicht, durch Reifen zu gehen, um die Berichterstattung künstlich hinzuzufügen.
EDIT: Wenn es keine Möglichkeit gibt, leben Sie einfach mit der leicht reduzierten Abdeckung. Denken Sie daran, dass die Abdeckung für Sie nützlich sein soll - Sie sollten für das Tool verantwortlich sein, nicht umgekehrt.
quelle
Ich stimme Jon Skeet nicht ganz zu. Ich denke, wenn Sie einen einfachen Gewinn erzielen können, um Berichterstattung zu erhalten und das Rauschen in Ihrem Bericht zu beseitigen, sollten Sie dies tun. Weisen Sie entweder Ihr Coverage-Tool an, den Konstruktor zu ignorieren, oder legen Sie den Idealismus beiseite und schreiben Sie den folgenden Test und fertig damit:
quelle
constructor
? Sollte nichtConstructor
parametriert werden und kein Rohtyp?constructor.isAccessible()
Gibt auch bei einem öffentlichen Konstruktor immer false zurück. Man sollte verwendenassertTrue(Modifier.isPrivate(constructor.getModifiers()));
.Obwohl es sich nicht unbedingt um eine Abdeckung handelt, habe ich diese Methode erstellt, um zu überprüfen, ob die Dienstprogrammklasse gut definiert ist, und um auch eine gewisse Abdeckung durchzuführen.
Ich habe den vollständigen Code und die Beispiele in https://github.com/trajano/maven-jee6/tree/master/maven-jee6-test platziert
quelle
Modifier.isPrivate
daisAccessible
ertrue
in einigen Fällen für private Konstruktoren zurückgegeben wurde (spöttische Bibliotheksinterferenz?).Assert.utilityClassWellDefined()
in JUnit 4.12+ zu haben . Haben Sie eine Pull-Anfrage in Betracht gezogen?setAccessible()
, um den Konstruktor zugänglich zu machen, Probleme für das Codeabdeckungstool von Sonar verursacht (wenn ich dies tue, verschwindet die Klasse aus den Codeabdeckungsberichten von Sonar).Ich hatte den Konstruktor meiner Klasse statischer Dienstprogrammfunktionen privat gemacht, um CheckStyle zu erfüllen. Aber wie auf dem Originalplakat hatte ich Cobertura, der sich über den Test beschwerte. Zuerst habe ich diesen Ansatz ausprobiert, aber dies hat keine Auswirkungen auf den Abdeckungsbericht, da der Konstruktor nie tatsächlich ausgeführt wird. Diese Tests werden also wirklich nur durchgeführt, wenn der Konstruktor privat bleibt - und dies wird durch die Zugänglichkeitsprüfung im nachfolgenden Test überflüssig.
Ich folgte dem Vorschlag von Javid Jamae und benutzte Reflexion, fügte aber Behauptungen hinzu, um jemanden zu erwischen, der mit der getesteten Klasse herumspielt (und nannte den Test, um High Levels Of Evil anzuzeigen).
Das ist so übertrieben, aber ich muss zugeben, dass ich das warme, verschwommene Gefühl einer 100% igen Methodenabdeckung mag.
quelle
fail(...)
ist nicht notwendig.Mit Java 8 ist es möglich, andere Lösungen zu finden.
Ich gehe davon aus, dass Sie einfach eine Utility-Klasse mit wenigen öffentlichen statischen Methoden erstellen möchten. Wenn Sie Java 8 verwenden können, können Sie
interface
stattdessen verwenden.Es gibt keinen Konstruktor und keine Beschwerde von Cobertura. Jetzt müssen Sie nur noch die Linien testen, die Ihnen wirklich wichtig sind.
quelle
Der Grund für das Testen von Code, der nichts bewirkt, besteht darin, eine 100% ige Codeabdeckung zu erreichen und festzustellen, wann die Codeabdeckung abnimmt. Ansonsten könnte man immer denken, hey ich habe keine 100% ige Codeabdeckung mehr, aber es ist wahrscheinlich wegen meiner privaten Konstruktoren. Dies macht es einfach, nicht getestete Methoden zu erkennen, ohne überprüfen zu müssen, ob es sich nur um einen privaten Konstruktor handelt. Wenn Ihre Codebasis wächst, fühlen Sie tatsächlich ein angenehm warmes Gefühl, wenn Sie 100% statt 99% betrachten.
IMO ist es am besten, hier Reflection zu verwenden, da Sie sonst entweder ein besseres Code-Coverage-Tool benötigen würden, das diese Konstruktoren ignoriert, oder das Code-Coverage-Tool anweisen müssten, die Methode (möglicherweise eine Anmerkung oder eine Konfigurationsdatei) zu ignorieren, da Sie dann stecken bleiben würden mit einem bestimmten Code-Coverage-Tool.
In einer perfekten Welt würden alle Tools zur Codeabdeckung private Konstruktoren ignorieren, die zu einer endgültigen Klasse gehören, da der Konstruktor als "Sicherheitsmaßnahme" nichts anderes enthält :)
Fügen Sie dem Array dann einfach Klassen hinzu, während Sie fortfahren.Ich würde diesen Code verwenden:
quelle
Neuere Versionen von Cobertura bieten integrierte Unterstützung für das Ignorieren trivialer Getter / Setter / Konstruktoren:
https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial
Trivial ignorieren
Das Ignorieren von Trivial ermöglicht das Ausschließen von Konstruktoren / Methoden, die eine Codezeile enthalten. Einige Beispiele umfassen den Aufruf nur eines Superkonstruktors, Getter / Setter-Methoden usw. Um das triviale Argument zum Ignorieren einzuschließen, fügen Sie Folgendes hinzu:
oder in einem Gradle Build:
quelle
Tu es nicht. Was bringt es, einen leeren Konstruktor zu testen? Da es in cobertura 2.0 eine Option gibt, solche trivialen Fälle (zusammen mit Setzern / Gettern) zu ignorieren, können Sie sie in maven aktivieren, indem Sie dem cobertura maven-Plugin einen Konfigurationsabschnitt hinzufügen:
Alternativ können Sie Coverage Annotations verwenden :
@CoverageIgnore
.quelle
Endlich gibt es eine Lösung!
quelle
Enum<E>
wirklich auf eine Aufzählung erstreckt ... Ich glaube, das zeigt die Absicht besser.Ich weiß nichts über Cobertura, aber ich verwende Clover und es hat die Möglichkeit, Musterausgleichsausschlüsse hinzuzufügen. Zum Beispiel habe ich Muster, die Apache-Commons-Protokollierungszeilen ausschließen, damit sie nicht in die Abdeckung einbezogen werden.
quelle
Eine andere Möglichkeit besteht darin, einen statischen Initialisierer zu erstellen, der dem folgenden Code ähnelt
Auf diese Weise wird der private Konstruktor als getestet betrachtet und der Laufzeitaufwand ist grundsätzlich nicht messbar. Ich mache das, um mit EclEmma eine 100% ige Abdeckung zu erhalten, aber wahrscheinlich funktioniert es für jedes Abdeckungstool. Der Nachteil dieser Lösung ist natürlich, dass Sie Produktionscode (den statischen Initialisierer) nur zu Testzwecken schreiben.
quelle
ClassUnderTest testClass = Whitebox.invokeConstructor (ClassUnderTest.class);
quelle
Manchmal markiert Cobertura Code, der nicht ausgeführt werden soll, als "nicht abgedeckt", daran ist nichts auszusetzen. Warum geht es Ihnen darum, statt
99%
Deckung zu haben?100%
?Technisch gesehen können Sie diesen Konstruktor zwar immer noch mit Reflexion aufrufen, aber das klingt für mich (in diesem Fall) sehr falsch.
quelle
Wenn ich die Absicht Ihrer Frage erraten würde, würde ich sagen:
Für 1 ist es offensichtlich, dass die gesamte Initialisierung über Factory-Methoden erfolgen soll. In solchen Fällen sollten Ihre Tests in der Lage sein, die Nebenwirkungen des Konstruktors zu testen. Dies sollte unter die Kategorie der normalen privaten Methodentests fallen. Verkleinern Sie die Methoden so, dass sie nur eine begrenzte Anzahl bestimmter Dinge ausführen (im Idealfall nur eine Sache und eine Sache gut), und testen Sie dann die Methoden, die auf ihnen beruhen.
Zum Beispiel, wenn mein [privat] Konstruktor setzt auf meine Klasse Instanzfelder
a
zu5
. Dann kann (oder muss) ich es testen:Für 2 können Sie clover so konfigurieren, dass Util-Konstruktoren ausgeschlossen werden, wenn Sie ein festgelegtes Namensmuster für Util-Klassen haben. ZB verwende ich in meinem eigenen Projekt so etwas (weil wir der Konvention folgen, dass Namen für alle Util-Klassen mit Util enden sollten):
Ich habe bewusst eine
.*
Gefolgschaft ausgelassen)
weil solche Konstruktoren keine Ausnahmen auslösen sollen (sie sollen nichts tun).Es kann natürlich einen dritten Fall geben, in dem Sie möglicherweise einen leeren Konstruktor für eine Nicht-Utility-Klasse haben möchten. In solchen Fällen würde ich empfehlen, dass Sie eine
methodContext
mit der genauen Signatur des Konstruktors eingeben.Wenn Sie viele solcher außergewöhnlichen Klassen haben, können Sie den von mir vorgeschlagenen verallgemeinerten privaten Konstruktor reg-ex ändern und daraus entfernen
Util
. In diesem Fall müssen Sie manuell sicherstellen, dass die Nebenwirkungen Ihres Konstruktors weiterhin getestet und von anderen Methoden in Ihrer Klasse / Ihrem Projekt abgedeckt werden.quelle
Test.java ist Ihre Quelldatei mit Ihrem privaten Konstruktor
quelle
Das Folgende hat mir bei einer Klasse geholfen, die mit der Lombok-Annotation @UtilityClass erstellt wurde und automatisch einen privaten Konstruktor hinzufügt.
Obwohl constructor.setAccessible (true) funktionieren sollte, wenn der private Konstruktor manuell geschrieben wurde, funktioniert die Lombok-Annotation nicht, da sie erzwungen wird. Constructor.newInstance () testet tatsächlich, ob der Konstruktor aufgerufen wird, und vervollständigt damit die Abdeckung des Costructors selbst. Mit assertThrows verhindern Sie, dass der Test fehlschlägt, und Sie haben die Ausnahme verwaltet, da genau der Fehler vorliegt, den Sie erwarten. Obwohl dies eine Problemumgehung ist und ich das Konzept der "Leitungsabdeckung" gegenüber der "Funktions- / Verhaltensabdeckung" nicht schätze, können wir bei diesem Test einen Sinn finden. Tatsächlich sind Sie sicher, dass die Utility-Klasse tatsächlich einen privaten Konstruktor hat, der eine Ausnahme korrekt auslöst, wenn er auch über eine Reflaktion aufgerufen wird. Hoffe das hilft.
quelle
Meine bevorzugte Option im Jahr 2019: Verwenden Sie Lombok.
Insbesondere die
@UtilityClass
Anmerkung . (Zum Zeitpunkt des Schreibens leider nur "experimentell", aber es funktioniert einwandfrei und hat einen positiven Ausblick, sodass es wahrscheinlich bald auf stabil aktualisiert wird.)Diese Annotation fügt den privaten Konstruktor hinzu, um eine Instanziierung zu verhindern, und macht die Klasse endgültig. In Kombination mit
lombok.addLombokGeneratedAnnotation = true
inlombok.config
ignorieren so gut wie alle Test-Frameworks den automatisch generierten Code bei der Berechnung der Testabdeckung, sodass Sie die Abdeckung dieses automatisch generierten Codes ohne Hacks oder Reflexionen umgehen können.quelle
Das kannst du nicht.
Sie erstellen anscheinend den privaten Konstruktor, um die Instanziierung einer Klasse zu verhindern, die nur statische Methoden enthalten soll. Anstatt zu versuchen, die Abdeckung dieses Konstruktors zu erhalten (was die Instanziierung der Klasse erfordern würde), sollten Sie ihn entfernen und Ihren Entwicklern vertrauen, dass sie der Klasse keine Instanzmethoden hinzufügen.
quelle