Parasoft-Logo Suche

Entdecken Sie das TÜV-zertifizierte GoogleTest mit Agentic AI für C/C++-Tests!
Details ansehen »

Parasoft-Blog

So erstellen Sie parametrisierte JUnit-Tests

By Brian McGlauflin • September 19, 2018 • 8 min gelesen
September 19, 2018 | 8 min gelesen
By Brian McGlauflin
Grafik eines Entwicklers, der Quellcode in mehreren Texteditor-Bildschirmen schreibt

Das Schreiben von parametrisiertem Testcode kann eine Menge Arbeit sein, da für jeden Test eine Menge korrekt geschriebener Code erforderlich ist. Glücklicherweise erfahren Sie hier, wie Sie Parasoft Jtest verwenden können, um Ihre parametrisierten Tests automatisch zu generieren, ohne Boilerplate-Codes schreiben zu müssen.

Parametrisierte Tests sind eine gute Möglichkeit, mehrere Testfälle zu definieren und auszuführen, wobei der einzige Unterschied zwischen ihnen die Daten sind. Hier sehen wir uns drei verschiedene Frameworks an, die üblicherweise bei JUnit-Tests verwendet werden.

Beim Schreiben von Komponententests werden üblicherweise Methodeneingabeparameter und erwartete Ergebnisse in der Testmethode selbst initialisiert. In einigen Fällen reicht es aus, einen kleinen Satz von Eingaben zu verwenden. Es gibt jedoch Fälle, in denen wir eine große Anzahl von Werten verwenden müssen, um alle Funktionen in unserem Code zu überprüfen. Parametrisierte Tests sind eine gute Möglichkeit, mehrere Testfälle zu definieren und auszuführen, wobei der einzige Unterschied zwischen ihnen die Daten sind. Sie können das Codeverhalten für eine Vielzahl von Werten, einschließlich Grenzfällen, validieren. Parametrisierungstests können die Codeabdeckung erhöhen und die Sicherheit bieten, dass der Code wie erwartet funktioniert.

Es gibt eine Reihe guter Parametrisierungs-Frameworks für Java. In diesem Artikel werden drei verschiedene Frameworks betrachtet, die üblicherweise bei JUnit-Tests verwendet werden, mit einem Vergleich zwischen ihnen und Beispielen für die Struktur der Tests. Schließlich werden wir untersuchen, wie die Erstellung parametrisierter Tests vereinfacht und beschleunigt werden kann.

JUnit-parametrisierte Test-Frameworks

Vergleichen wir die drei gängigsten Frameworks: JUnit 3, JunitParams und JUnit 4. Jedes JUnit-Parametrisierungsframework hat seine eigenen Stärken und Schwächen.

Einheit 4

Vorteile:

  • Dies ist das in JUnit 4 integrierte Parametrisierungsframework, sodass keine zusätzlichen externen Abhängigkeiten erforderlich sind.
  • Es unterstützt ältere Versionen von Java (JDK 7 und älter).

Nachteile:

  • Testklassen verwenden Felder und Konstruktoren, um Parameter zu definieren, die Tests ausführlicher machen.
  • Für jede getestete Methode ist eine separate Testklasse erforderlich.

JunitParams

Vorteile:

  • Vereinfacht die Parametersyntax, indem Parameter direkt an eine Testmethode übergeben werden können.
  • Ermöglicht mehrere Testmethoden (jede mit ihren eigenen Daten) pro Testklasse.
  • Unterstützt CSV-Datenquellen sowie annotationsbasierte Werte (keine Methode erforderlich).

Nachteile:

  • Erfordert, dass das Projekt mit der JunitParams-Abhängigkeit konfiguriert wird.
  • Beim Ausführen und Debuggen von Tests müssen alle Tests innerhalb der Klasse ausgeführt werden. Es ist nicht möglich, eine einzelne Testmethode innerhalb einer Testklasse auszuführen.

Einheit 5

Vorteile:

  • Dieses Parametrisierungsframework ist in JUnit 5 integriert und verbessert das, was in JUnit 4 enthalten war.
  • Hat eine vereinfachte Parametersyntax wie JunitParams.
  • Unterstützt mehrere Datensatzquellentypen, einschließlich CSV und Annotation (keine Methode erforderlich).
  • Obwohl keine zusätzlichen Abhängigkeiten erforderlich sind, wird mehr als eine .jar-Datei benötigt.

Nachteile:

  • Benötigt Java 8 und eine neuere Version des Build-Systems (Gradle 4.6 oder Maven Surefire 2.21).
  • Wird in Ihrer IDE möglicherweise noch nicht unterstützt (derzeit unterstützen nur Eclipse und IntelliJ JUnit 5).

Beispiel

Angenommen, wir haben eine Methode, mit der Kreditanträge für eine Bank verarbeitet werden. Möglicherweise schreiben wir einen Komponententest, der den Betrag der Kreditanfrage, den Anzahlungsbetrag und andere Werte angibt. Wir würden dann Zusicherungen erstellen, die die Antwort bestätigen - das Darlehen kann genehmigt oder abgelehnt werden, und in der Antwort können die Bedingungen des Darlehens angegeben werden.

Beispielsweise:

public LoanResponse requestLoan(float loanAmount, float downPayment, float availableFunds)
{
LoanResponse response = new LoanResponse();
response.setApproved(true);

if (availableFunds < downPayment) {
response.setApproved(false);
response.setMessage("error.insufficient.funds.for.down.payment");
return response;
}
if (downPayment / loanAmount < 0.1) {
response.setApproved(false);
response.setMessage("error.insufficient.down.payment");
}

return response;
}

Roh anzeigen

parametrized_test_example_1.java

Gehostet von GitHub

Schauen wir uns zunächst einen regelmäßigen Test für die oben beschriebene Methode an:

@Test
public void testRequestLoan() throws Throwable
{
// Given
LoanProcessor underTest = new LoanProcessor();

// When
LoanResponse result = underTest.requestLoan(1000f, 200f, 250f);

// Then
assertNotNull(result);
assertTrue(result.isApproved());
assertNull(result.getMessage());
}

Roh anzeigen

parametrized_test_example_2.java

Gehostet von GitHub

In diesem Beispiel testen wir unsere Methode, indem wir ein Darlehen in Höhe von 1000 USD mit einer Anzahlung von 200 USD anfordern und angeben, dass dem Antragsteller 250 USD an verfügbaren Mitteln zur Verfügung stehen. Der Test bestätigt dann, dass das Darlehen genehmigt wurde und in der Antwort keine Nachricht enthalten ist.

Um sicherzustellen, dass unsere Methode `requestLoan()` umfassend getestet wird, müssen wir verschiedene Anzahlungen, Kreditbeträge und verfügbare Mittel berücksichtigen. Testen wir beispielsweise einen Kreditantrag über 1 Million US-Dollar ohne Anzahlung, der abgelehnt werden sollte. Wir könnten den bestehenden Test einfach mit anderen Werten wiederholen, da die Testlogik jedoch dieselbe wäre, ist es effizienter, den Test zu parametrisieren.

Wir werden den angeforderten Darlehensbetrag, die Anzahlung und die verfügbaren Mittel sowie die erwarteten Ergebnisse parametrisieren: ob das Darlehen genehmigt wurde und die Nachricht nach der Validierung zurückgegeben wird. Jeder Satz von Anforderungsdaten wird zusammen mit den erwarteten Ergebnissen zu einem eigenen Testfall.

Ein Beispiel für einen parametrisierten Test mit JUnit 4 Parametrisiert

Beginnen wir mit einem parametrisierten Junit 4-Beispiel. Um einen parametrisierten Test zu erstellen, müssen wir zuerst die Variablen für den Test definieren. Wir müssen auch einen Konstruktor einbinden, um sie zu initialisieren:

@RunWith(Parameterized.class)
public class LoanProcessorParameterizedTest {
float loanAmount;
float downPayment;
float availableFunds;
boolean expectApproved;
String expectedMessage;
public LoanProcessorParameterizedTest(float loanAmount, float downPayment,
float availableFunds, boolean expectApproved, String expectedMessage)
{
this.loanAmount = loanAmount;
this.downPayment = downPayment;
this.availableFunds = availableFunds;
this.expectApproved = expectApproved;
this.expectedMessage = expectedMessage;
}
// ...
}

Roh anzeigen

parametrized_test_example_3.java

Gehostet von GitHub

Hier sehen wir, dass der Test die Annotation `@RunWith` verwendet , um anzugeben, dass er mit dem parametrisierten JUnit4-Runner ausgeführt wird. Dieser Runner sucht nach einer Methode, die die Wertemenge für den Test (annotiert mit `@Parameters` ) bereitstellt, initialisiert den Test korrekt und führt ihn mit mehreren Zeilen aus.

Beachten Sie, dass jeder Parameter als Feld in der Testklasse definiert ist und der Konstruktor diese Werte initialisiert (alternativ können Sie Werte mithilfe der Annotation `@Parameter` in Felder einfügen , wenn Sie keinen Konstruktor erstellen möchten). Für jede Zeile im Werteset instanziiert der Parameterized Runner die Testklasse und führt jeden Test in dieser Klasse aus.

Fügen wir eine Methode hinzu, die dem Parameterized Runner die Parameter bereitstellt:

@Parameters(name = "Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
public static Iterable<Object[]> data() throws Throwable
{
return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null }
});
}

Roh anzeigen

parametrized_test_example_4.java

Gehostet von GitHub

Die Wertemengen werden von der Methode `data()` als Liste von Objekt -Arrays erstellt, die mit `@Parameters` annotiert ist . Beachten Sie, dass `@Parameters` den Testnamen mithilfe von Platzhaltern festlegt, die beim Testlauf ersetzt werden. Dadurch lassen sich die Werte in den Testergebnissen leichter erkennen, wie wir später sehen werden. Aktuell gibt es nur eine Datenzeile, die einen Fall testet, in dem der Kredit genehmigt werden soll. Wir können weitere Zeilen hinzufügen, um die Testabdeckung der Methode zu erhöhen.

@Parameters(name = "Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
public static Iterable<Object[]> data() throws Throwable
{
return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment" },
{ 1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment" }
});
}

Roh anzeigen

parametrized_test_example_5.java

Gehostet von GitHub

Hier haben wir einen Testfall, in dem das Darlehen genehmigt werden würde, und zwei Fälle, in denen es aus unterschiedlichen Gründen nicht genehmigt werden sollte. Möglicherweise möchten wir Zeilen hinzufügen, in denen Null- oder negative Werte verwendet werden, sowie Randbedingungen testen.

Wir sind jetzt bereit, die Testmethode zu erstellen:

@Test
public void testRequestLoan() throws Throwable
{
// Given
LoanProcessor underTest = new LoanProcessor();

// When
LoanResponse result = underTest.requestLoan(loanAmount, downPayment, availableFunds);

// Then
assertNotNull(result);
assertEquals(expectApproved, result.isApproved());
assertEquals(expectedMessage, result.getMessage());
}

Roh anzeigen

parametrized_test_example_6.java

Gehostet von GitHub

Hier greifen wir beim Aufruf der Methode requestLoan() auf die Felder zu und überprüfen die Ergebnisse.

JunitParams Beispiel

Die JUnitParams-Bibliothek vereinfacht die Syntax parametrisierter Tests, indem sie das direkte Übergeben von Parametern an die Testmethode ermöglicht. Die Parameterwerte werden von einer separaten Methode bereitgestellt, deren Name in der @Parameters -Annotation angegeben wird.

@RunWith(JUnitParamsRunner.class)
public class LoanProcessorParameterizedTest2 {

@Test
@Parameters(method = "testRequestLoan_Parameters")
public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
{
...
}

@SuppressWarnings("unused")
private static Object[][] testRequestLoan_Parameters() throws Throwable {
// Parameters: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}
return new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment"},
{ 1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment" }
};
}
}

Roh anzeigen

parametrized_test_example_7.java

Gehostet von GitHub

JunitParams bietet den zusätzlichen Vorteil, dass die Verwendung von CSV-Dateien zur Bereitstellung von Werten zusätzlich zur Bereitstellung der Werte im Code unterstützt wird. Dadurch kann der Test von den zu aktualisierenden Daten und Datenwerten entkoppelt werden, ohne den Code zu aktualisieren.

Junit 5 Beispiel

JUnit 5 behebt einige der Einschränkungen und Mängel von JUnit 4. Wie JunitParams vereinfacht auch Junit 5 die Syntax parametrisierter Tests. Die wichtigsten Änderungen in der Syntax sind:

  • Die Testmethode ist mit kommentiert @ParametrierterTest statt @Prüfung
  • Die Testmethode akzeptiert Parameter direkt, anstatt Felder und einen Konstruktor zu verwenden
  • Das @ RunWith Annotation wird nicht mehr benötigt

Das Definieren des gleichen Beispiels in Junit 5 würde folgendermaßen aussehen:

public class LoanProcessorParameterizedTest {

@ParameterizedTest(name="Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
@MethodSource("testRequestLoan_Parameters")
public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
{
...
}

static Stream<Arguments> testRequestLoan_Parameters() throws Throwable {
return Stream.of(
Arguments.of(1000.0f, 200.0f, 250.0f, true, null),
Arguments.of(1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment"),
Arguments.of(1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment")
);
}
}

Roh anzeigen

parametrized_test_example_8.java

Gehostet von GitHub

Erstellen Sie effizient parametrisierte Tests

Wie man sich vorstellen kann, ist das Schreiben der oben beschriebenen parametrisierten Tests etwas aufwendig. Jedes Framework für parametrisierte Tests erfordert etwas Boilerplate-Code, der korrekt geschrieben werden muss. Es kann schwierig sein, sich die korrekte Struktur zu merken, und das Schreiben kostet Zeit. Um dies deutlich zu vereinfachen, können Sie Parasoft Jtest verwenden , um parametrisierte Tests wie die oben beschriebenen automatisch zu generieren. Wählen Sie dazu einfach die Methode aus, für die Sie einen Test generieren möchten (in Eclipse oder IntelliJ):

Screenshot vom Erstellen eines parametrisierten Tests in Jtest

Der Test wird unter Verwendung von Standardwerten und Zusicherungen generiert. Anschließend können Sie den Test mit realen Eingabewerten und Zusicherungen konfigurieren und der data () -Methode weitere Datenzeilen hinzufügen.

Ausführen des parametrisierten Tests

Parasoft Jtest kann parametrisierte Tests direkt in Eclipse und IntelliJ ausführen.

JUnit-Ansicht in Jtest in der Eclipse IDE

Die JUnit-Ansicht in Eclipse

Beachten Sie, dass der Name jedes Tests, wie gezeigt, Eingabewerte aus dem Datensatz und erwartete Ergebniswerte enthält. Dies kann das Debuggen des Tests erheblich vereinfachen, wenn er fehlschlägt, da die Eingabeparameter und erwarteten Ausgaben für jeden Fall angezeigt werden.

Sie können auch die Aktion Alle ausführen von Parasoft Jtest verwenden:

Flussbaumansicht in Parasoft Jtest

Die Flow Tree-Ansicht in Parasoft Jtest

Es analysiert den Testablauf und liefert detaillierte Informationen zum vorherigen Testlauf. Auf diese Weise können Sie sehen, was im Test passiert ist, ohne den Test mit Haltepunkten oder Debugging-Anweisungen erneut ausführen zu müssen. Beispielsweise können Sie parametrisierte Werte in der Variablenansicht anzeigen:

Variablenansicht in Parasoft Jtest

Die Variablenansicht in Parasoft Jtest

Fazit

Alle drei von uns vorgestellten Frameworks sind eine gute Wahl und funktionieren einwandfrei. Bei JUnit 4 bevorzuge ich JunitParams gegenüber dem integrierten parametrisierten JUnit-4-Framework, da die Testklassen übersichtlicher gestaltet sind und sich mehrere Testmethoden in derselben Klasse definieren lassen. Bei JUnit 5 hingegen empfehle ich das integrierte JUnit-5-Framework, da es die Schwächen von JUnit 4 behebt und keine zusätzlichen Bibliotheken benötigt. Ich nutze außerdem gerne die Unit-Testing-Funktionen von Parasoft, um das Erstellen, Ausführen und Debuggen parametrisierter Tests effizienter zu gestalten.

Erfahren Sie, wie Sie mit Parasoft Jtest Ihre Java-Codequalität und Teamproduktivität verbessern können.

Demo anfordern