Entdecken Sie das TÜV-zertifizierte GoogleTest mit Agentic AI für C/C++-Tests!
Details ansehen »
Parasoft-Blog
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.
Zum Abschnitt springen
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.
Vergleichen wir die drei gängigsten Frameworks: JUnit 3, JunitParams und JUnit 4. Jedes JUnit-Parametrisierungsframework hat seine eigenen Stärken und Schwächen.
Vorteile:
Nachteile:
Vorteile:
Nachteile:
Vorteile:
Nachteile:
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;
}
parametrized_test_example_1.java
@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());
}
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.
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;
}
// ...
}
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 }
});
}
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" }
});
}
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());
}
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.
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" }
};
}
}
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 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:
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")
);
}
}
parametrized_test_example_8.java
Gehostet von GitHub
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):
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.
Parasoft Jtest kann parametrisierte Tests direkt in Eclipse und IntelliJ ausführen.
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:
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:
Die Variablenansicht in Parasoft Jtest
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.