Junit - einmalige Setup-Methode

119

Ich habe eine Klasse mit ein paar Tests eingerichtet und @Beforemöchte nicht eine Setup-Methode verwenden, die vor allen Tests nur einmal ausgeführt wird. Ist das mit Junit 4.8 möglich?

Bober02
quelle
1
Schauen Sie sich RunListener an: stackoverflow.com/a/14773170/548473
Grigory Kislin

Antworten:

205

Obwohl ich @assylias zustimme, dass die Verwendung @BeforeClasseine klassische Lösung ist, ist sie nicht immer bequem. Die mit annotierte Methode @BeforeClassmuss statisch sein. Dies ist für einige Tests, die eine Testfallinstanz benötigen, sehr unpraktisch. Zum Beispiel Spring-basierte Tests, @Autowiredmit denen mit im Spring-Kontext definierten Diensten gearbeitet wird.

In diesem Fall verwende ich persönlich eine reguläre setUp()Methode, die mit @BeforeAnmerkungen versehen ist, und verwalte mein benutzerdefiniertes static(!) booleanFlag:

private static boolean setUpIsDone = false;
.....
public void setUp() {
    if (setUpIsDone) {
        return;
    }
    // do the setup
    setUpIsDone = true;
}
AlexR
quelle
10
Hinzufügen zu Kenny Casons Kommentar, warum es statisch sein muss. Es muss statisch sein, da JUnit für jede @ Test-Methode eine neue Instanz der Testklasse instanziiert. Die Instanzvariable wird für jede Instanz auf den Standardwert (false) zurückgesetzt, wenn sie nicht statisch ist. Weitere Informationen finden Sie unter: martinfowler.com/bliki/JunitNewInstance.html
Dustin.schultz
2
Dies funktioniert nur in dem Fall, in dem sich die setUp()Methode in einer Oberklasse befindet. Sie haben unten eine Antwort veröffentlicht , um dies zu beheben.
Steve Chambers
4
Ich zögere, dies jemandem mit 84.000 Mitarbeitern zu sagen, aber BeforeClass beantwortet die Frage tatsächlich nicht: BeforeClass wird zu Beginn jeder Testklasse ausgeführt. Das OP forderte jedoch eine, die "nur einmal vor allen Tests" ausgeführt wird. Ihre vorgeschlagene Lösung könnte dies tun, aber Sie müssten alle Ihre Testklassen dazu bringen, eine "CommonTest" -Klasse zu erweitern ...
Mike Nagetier
1
@mikerodent, IMHO OP fragte nach allen Tests in seinem Testfall, nicht nach allen Tests insgesamt. Ihr Kommentar ist also weniger relevant. Übrigens, machen Sie sich keine Sorgen, jemandem etwas zu sagen, auch wenn sein Ruf hoch ist. Zumindest mache ich das :). Und mein Ruf war im August 2012 deutlich niedriger, als ich die Frage beantwortete.
AlexR
Funktioniert in meinem Fall nicht. Im Setup initialisierte Variablen werden nach jedem Test zurückgesetzt, daher ist es sinnlos, nur einmal zu initialisieren.
Aphax
89

Sie können die BeforeClassAnmerkung verwenden :

@BeforeClass
public static void setUpClass() {
    //executed only once, before the first test
}
Assylien
quelle
12
Ich kann dies nicht verwenden, ich habe einige Setup-Methoden, die auf nicht statischen Komponenten wie getClass ()
Bober02
1
@ Bober02 BeforeClass muss tatsächlich statisch sein. Wenn Sie das nicht verwenden können, bietet die andere Antwort eine Problemumgehung.
Assylias
2
Sicher, dass Sie nicht TheClassYouWant.classanstelle Ihres getClass () -Aufrufs verwenden können? Dies ist tatsächlich Java : String.class.getName().
Stolsvik
1
@mikerodent Ich habe die Frage als "alle Tests in der Klasse" verstanden - aber Sie haben Recht, es ist möglicherweise nicht das, was das OP wollte.
Assylias
29

JUnit 5 hat jetzt eine @ BeforeAll-Annotation:

Gibt an, dass die mit Anmerkungen versehene Methode vor allen @ Test-Methoden in der aktuellen Klasse oder Klassenhierarchie ausgeführt werden soll. analog zu @UeClass von JUnit 4. Solche Methoden müssen statisch sein.

Die Lebenszyklusanmerkungen von JUnit 5 scheinen es endlich richtig gemacht zu haben! Sie können erraten, welche Anmerkungen verfügbar sind, ohne auch nur nachzuschauen (z. B. @BeforeEach @AfterAll).

Brian
quelle
6
Es hat das gleiche Problem war @BeforeClass, es muss sein static. Die Lösung von IMO @ AlexR ist besser.
Zengr
@zengr stimmt Ihnen eher zu: Wie ich bereits zu AlexR gesagt habe, erfordert seine Lösung, dass alle Testklassen von einer CommonTest-Klasse untergeordnet werden, wenn sie nur einmal ausgeführt werden sollen. Aber es ist so einfach wie es nur sein kann, und meiner Meinung nach sollten Sie wahrscheinlich keine "ausgefallene" Framework-Lösung verwenden, wenn ein einfacher Mechanismus in der Sprache verfügbar ist. Es sei denn, es gibt natürlich einen guten Grund. Auch die Verwendung einer einfachen Sache wie seiner mit einem guten Typnamen "macht, was auf der Dose steht" hilft bei der Lesbarkeit.
Mike Nagetier
Trotzdem scheint es meiner Meinung nach viel mehr Rechtfertigung für eine "AfterAll" -Anmerkung zu geben: Es wäre sehr schwierig und erfunden, einen Mechanismus zu entwickeln, um zu erkennen, wann alle Tests durchgeführt wurden. Umgekehrt werden Puristen natürlich wahrscheinlich sagen, dass Sie niemals eine "endgültige Bereinigung" durchführen müssen sollten, dh dass jeder "TearDown" alle Ressourcen in einem makellosen Zustand belassen sollte ... und sie haben wahrscheinlich Recht!
Mike Nagetier
Funktioniert dies mit Maven, wo es mehrere Module mit jeweils ihren Tests gibt?
Mark Boon
@mike rodent, in meinem Fall scheint das Einrichten und Herunterfahren von Testdateien im Dateisystem vor / nach jedem Test zu Deadlocks in den Dateien zu führen. Im Moment bin ich unabhängig zu AlexRs Lösung für die einmalige Einrichtung gekommen. Ich habe zwei statische Flags, bereits eingerichtet und schmutzig. setup () ruft cleanup () auf, wenn anfänglich ein fehlerhafter Zustand erkannt wird oder wenn ein Setup-Fehler zu einem fehlerhaften Zustand führt. Um nach dem Ausführen von Tests aufzuräumen, führe ich sie erneut aus. Chaotisch, überhaupt nicht ideal, nicht in unserem Build-Prozess. Immer noch auf der Suche nach einem besseren Weg (jUnit 4.12).
Rebeccah
9

Wenn setUp()es sich um eine Oberklasse der Testklasse handelt (z. B. AbstractTestBaseunten), kann die akzeptierte Antwort wie folgt geändert werden:

public abstract class AbstractTestBase {
    private static Class<? extends AbstractTestBase> testClass;
    .....
    public void setUp() {
        if (this.getClass().equals(testClass)) {
            return;
        }

        // do the setup - once per concrete test class
        .....
        testClass = this.getClass();
    }
}

Dies sollte für eine einzelne nicht statische setUp()Methode funktionieren , aber ich kann kein Äquivalent dafür erstellen, tearDown()ohne in eine Welt komplexer Reflexion zu geraten ... Kopfgeldpunkte für jeden, der es kann!

Steve Chambers
quelle
3

Bearbeiten: Ich habe gerade beim Debuggen herausgefunden, dass die Klasse auch vor jedem Test instanziiert wird. Ich denke, die @ BeforeClass-Annotation ist hier die beste.

Sie können auch auf dem Konstruktor einrichten, die Testklasse ist schließlich eine Klasse. Ich bin mir nicht sicher, ob es eine schlechte Praxis ist, da fast alle anderen Methoden mit Anmerkungen versehen sind, aber es funktioniert. Sie könnten einen solchen Konstruktor erstellen:

public UT () {
    // initialize once here
}
@Test
// Some test here...

Der ctor wird vor den Tests aufgerufen, da sie nicht statisch sind.


quelle
0

Versuchen Sie diese Lösung: https://stackoverflow.com/a/46274919/907576 :

Mit @BeforeAllMethods/ @AfterAllMethodsannotation können Sie jede Methode in der Testklasse in einem Instanzkontext ausführen, in dem alle injizierten Werte verfügbar sind.

Radistao
quelle
Verlässt sich auf eine Bibliothek eines Drittanbieters.
Andrew
0

Meine schmutzige Lösung ist:

public class TestCaseExtended extends TestCase {

    private boolean isInitialized = false;
    private int serId;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        if(!isInitialized) {
            loadSaveNewSerId();
            emptyTestResultsDirectory();
            isInitialized = true;
        }
    }

   ...

}

Ich verwende es als Basis für alle meine Testfälle.

Obi Zwei
quelle
öffentliche Klasse TestCaseExtended erweitert TestCase {private static boolean isInitialized = false; private static TestCaseExtended caseExtended; private int serId; @Override public void setUp () löst eine Ausnahme aus {super.setUp (); if (! isInitialized) {caseExtended = new TestCaseExtended (); caseExtended.loadSaveNewSerId (); caseExtended.emptyTestResultsDirectory (); isInitialized = true; }}
Obi Two
0

Wenn Sie keine Deklaration einer Variablen erzwingen möchten, die für jeden Subtest festgelegt und überprüft wird, können Sie dies einem SuperTest hinzufügen:

public abstract class SuperTest {

    private static final ConcurrentHashMap<Class, Boolean> INITIALIZED = new ConcurrentHashMap<>();
    protected final boolean initialized() {
        final boolean[] absent = {false};
        INITIALIZED.computeIfAbsent(this.getClass(), (klass)-> {
            return absent[0] = true;
        });
        return !absent[0];
    }
}



public class SubTest extends SuperTest {
    @Before
    public void before() {
        if ( super.initialized() ) return;

         ... magic ... 
    }

}
mmm
quelle
0

Ich habe dieses Problem folgendermaßen gelöst:

In Ihre Basis abstrakte Klasse (ich meine abstrakte Klasse , wo Sie Ihre Treiber in initialisieren setUpDriver () Methode) diesen Teil des Codes:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

Und jetzt, wenn Ihre Testklassen werden aus erstreckt Basis abstrakter Klasse -> setUpDriver () Methode wird vor dem ersten @Test nur ausgeführt werden , ONE pro Laufzeit.

Sergii
quelle
0

Verwenden Sie die @ PostConstruct-Methode von Spring, um alle Initialisierungsarbeiten auszuführen. Diese Methode wird ausgeführt, bevor einer der @ Test ausgeführt wird

Abhishek Chatterjee
quelle