Testen ist ein sehr wichtiger Aspekt der Entwicklung und kann das Schicksal einer Anwendung weitgehend bestimmen. Gute Tests können Probleme erkennen, die dazu führen, dass Ihre Anwendung frühzeitig abstürzt, schlechte Tests führen jedoch häufig zu Ausfällen und ständigen Ausfallzeiten.
Während es drei Haupttypen von Softwaretests gibt: Unit-Tests, Funktionstests und Integrationstests, werden wir in diesem Blogbeitrag Unit-Tests auf Entwicklerebene besprechen. Bevor ich auf die Einzelheiten eingehe, werfen wir einen Blick auf die Details jedes dieser drei Tests.
Unit-Tests werden verwendet, um einzelne Codekomponenten zu testen und sicherzustellen, dass der Code wie erwartet funktioniert. Unit-Tests werden von Entwicklern geschrieben und ausgeführt. Meistens wird ein Testframework wie JUnit oder TestNG verwendet. Testfälle werden normalerweise auf Methodenebene geschrieben und durch Automatisierung ausgeführt. Integrationstests prüfen, ob das System als Ganzes funktioniert. Integrationstests werden ebenfalls von Entwicklern durchgeführt, aber anstatt eine einzelne Komponente zu testen, sind sie darauf ausgelegt, komponentenübergreifend zu testen. Das System besteht aus vielen einzelnen Komponenten wie Code, Datenbank, Webserver usw. Integrationstests können Probleme wie Komponentenverkabelung, Netzwerkzugriff, Datenbankprobleme usw. aufdecken.
Funktionstests prüfen, ob jede Funktion korrekt implementiert ist, indem sie die Ergebnisse einer bestimmten Eingabe mit der Spezifikation vergleichen. Normalerweise geschieht dies nicht auf Entwicklerebene. Funktionstests werden von einem separaten Testteam durchgeführt. Testfälle werden auf der Grundlage von Spezifikationen geschrieben und die tatsächlichen Ergebnisse mit den erwarteten Ergebnissen verglichen. Für automatisierte Funktionstests stehen mehrere Tools zur Verfügung, beispielsweise Selenium und QTP.
Wie bereits erwähnt, helfen Unit-Tests Entwicklern dabei, festzustellen, ob der Code ordnungsgemäß funktioniert. In diesem Blogbeitrag gebe ich nützliche Tipps für Unit-Tests in Java.
1. Frameworks für Unit-Tests verwenden
2. Verwenden Sie testgetriebene Entwicklung mit Vorsicht!
Das Ziel besteht darin, Tests zu schreiben, die alle Anforderungen abdecken, anstatt überhaupt Code zu schreiben, der möglicherweise nicht einmal die Anforderungen erfüllt. TDD ist großartig, weil es zu einfachem
modularemCode führt, der leicht zu warten ist. Die Gesamtentwicklungsgeschwindigkeit wird beschleunigt und Fehler werden leichter gefunden. Darüber hinaus entstehen Unit-Tests als Nebenprodukt des TDD-Ansatzes. TDD ist jedoch möglicherweise nicht für alle Situationen geeignet. Bei Projekten mit komplexen Designs kann die Konzentration auf das einfachste Design, um das Bestehen von Testfällen zu erleichtern, ohne vorauszudenken, zu großen Codeänderungen führen. Darüber hinaus sind TDD-Methoden schwierig für Systeme zu verwenden, die mit Legacy-Systemen, GUI-Anwendungen oder Anwendungen, die mit Datenbanken arbeiten, interagieren. Darüber hinaus müssen Tests aktualisiert werden, wenn sich der Code ändert.
Daher sollten vor der Entscheidung für den TDD-Ansatz die oben genannten Faktoren berücksichtigt und entsprechend der Art des Projekts Maßnahmen ergriffen werden.
3. Codeabdeckung messen
Stellen Sie sicher, dass es Testfälle gibt, die alle Zweige des Codes abdecken, d. h. if/else-Anweisungen.
Eine hohe Codeabdeckung garantiert kein perfektes Testen, seien Sie also vorsichtig!
Die concat
-Methode unten akzeptiert einen booleschen Wert als Eingabe und übergibt zusätzlich zwei Strings nur, wenn der boolesche Wert wahr ist:
public String concat(boolean append, String a,String b) { String result = null; If (append) { result = a + b; } return result.toLowerCase(); }
Das Folgende ist Test Fall für die obige Methode:
@Test public void testStringUtil() { String result = stringUtil.concat(true, "Hello ", "World"); System.out.println("Result is "+result); }
In diesem Fall ist der Wert der Testausführung wahr. Wenn der Test ausgeführt wird, ist er erfolgreich. Wenn das Codeabdeckungstool ausgeführt wird, zeigt es eine Codeabdeckung von 100 % an, da der gesamte Code in der Methode concat
ausgeführt wurde. Wenn der Test jedoch mit dem Wert „false“ ausgeführt wird, wird NullPointerException
ausgelöst. Eine 100-prozentige Codeabdeckung bedeutet also nicht wirklich, dass die Tests alle Szenarien abdecken, und auch nicht, dass die Tests gut sind.
Vor JUnit4 mussten die Daten für die Ausführung des Testfalls fest in den Testfall codiert werden. Dies führt zu der Einschränkung, dass der Testfallcode geändert werden muss, um Tests mit unterschiedlichen Daten auszuführen. Sowohl JUnit4 als auch TestNG unterstützen jedoch die Externalisierung von Testdaten, sodass Testfälle für verschiedene Datensätze ausgeführt werden können, ohne den Quellcode zu ändern.
Die folgende MathChecker
-Klasse verfügt über Methoden, um zu überprüfen, ob eine Zahl ungerade ist:
public class MathChecker { public Boolean isOdd(int n) { if (n%2 != 0) { return true; } else { return false; } } }
Das Folgende ist der TestNG-Testfall für die MathChecker-Klasse:
public class MathCheckerTest { private MathChecker checker; @BeforeMethod public void beforeMethod() { checker = new MathChecker(); } @Test @Parameters("num") public void isOdd(int num) { System.out.println("Running test for "+num); Boolean result = checker.isOdd(num); Assert.assertEquals(result, new Boolean(true)); } }
Das Folgende ist testng.xml (die Konfigurationsdatei für TestNG), die die Daten enthält, für die die Tests durchgeführt werden sollen:
<?xml version="1.0" encoding="UTF-8"?> <suite name="ParameterExampleSuite" parallel="false"> <test name="MathCheckerTest"> <classes> <parameter name="num" value="3"></parameter> <class name="com.stormpath.demo.MathCheckerTest"/> </classes> </test> <test name="MathCheckerTest1"> <classes> <parameter name="num" value="7"></parameter> <class name="com.stormpath.demo.MathCheckerTest"/> </classes> </test> </suite>
Wie zu sehen ist, In diesem Fall wird der Test zweimal ausgeführt, jeweils einmal für die Werte 3 und 7. Neben der Angabe von Testdaten über XML-Konfigurationsdateien können Testdaten auch in Klassen über DataProvider-Annotationen bereitgestellt werden.
Ähnlich wie TestNG können Testdaten auch für die Verwendung mit JUnit externalisiert werden. Das Folgende ist ein JUnit-Testfall für dieselbe MathChecker-Klasse wie oben:
@RunWith(Parameterized.class) public class MathCheckerTest { private int inputNumber; private Boolean expected; private MathChecker mathChecker; @Before public void setup(){ mathChecker = new MathChecker(); } // Inject via constructor public MathCheckerTest(int inputNumber, Boolean expected) { this.inputNumber = inputNumber; this.expected = expected; } @Parameterized.Parameters public static Collection<Object[]> getTestData() { return Arrays.asList(new Object[][]{ {1, true}, {2, false}, {3, true}, {4, false}, {5, true} }); } @Test public void testisOdd() { System.out.println("Running test for:"+inputNumber); assertEquals(mathChecker.isOdd(inputNumber), expected); } }
Wie zu sehen ist, werden die Testdaten, für die der Test durchgeführt werden soll, durch die Methode getTestData() angegeben. Diese Methode kann leicht geändert werden, um Daten aus einer externen Datei zu lesen, anstatt die Daten fest zu codieren.
Viele unerfahrene Entwickler sind es gewohnt, nach jeder Codezeile System.out.println-Anweisungen zu schreiben, um zu überprüfen, ob der Code korrekt ausgeführt wird. Diese Praxis erstreckt sich häufig auf Unit-Tests, was zu einem unübersichtlichen Testcode führt. Abgesehen von der Verwirrung erfordert dies einen manuellen Eingriff des Entwicklers, um die auf der Konsole gedruckte Ausgabe zu überprüfen und zu überprüfen, ob der Test erfolgreich ausgeführt wurde. Ein besserer Ansatz besteht darin, Behauptungen zu verwenden, die automatisch Testergebnisse anzeigen.
Die folgende StringUti
-Klasse ist eine einfache Klasse mit einer Methode, die zwei Eingabezeichen verbindet und das Ergebnis zurückgibt:
public class StringUtil { public String concat(String a,String b) { return a + b; } }
Das Folgende ist die obige Methode Die beiden Komponententests:
@Test public void testStringUtil_Bad() { String result = stringUtil.concat("Hello ", "World"); System.out.println("Result is "+result); } @Test public void testStringUtil_Good() { String result = stringUtil.concat("Hello ", "World"); assertEquals("Hello World", result); }
testStringUtil_Bad werden immer bestanden, da sie keine Behauptungen enthalten. Entwickler müssen die Testausgabe auf der Konsole manuell überprüfen. testStringUtil_Good schlägt fehl, wenn die Methode falsche Ergebnisse zurückgibt und kein Eingreifen des Entwicklers erfordert.
Einige Methoden haben keine deterministischen Ergebnisse, d. h. die Ausgabe der Methode ist nicht im Voraus bekannt und kann sich jedes Mal ändern. Betrachten Sie beispielsweise den folgenden Code, der über eine komplexe -Funktion und eine Methode verfügt, die die Zeit (in Millisekunden) berechnet, die zum Ausführen der komplexen Funktion benötigt wird:
public class DemoLogic { private void veryComplexFunction(){ //This is a complex function that has a lot of database access and is time consuming //To demo this method, I am going to add a Thread.sleep for a random number of milliseconds try { int time = (int) (Math.random()*100); Thread.sleep(time); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public long calculateTime(){ long time = 0; long before = System.currentTimeMillis(); veryComplexFunction(); long after = System.currentTimeMillis(); time = after - before; return time; } }
In diesem Fall , wird bei jeder Ausführung der calculateTime
-Methode ein anderer Wert zurückgegeben. Das Schreiben von Testfällen für diese Methode ist nutzlos, da die Ausgabe dieser Methode variabel ist. Daher kann die Testmethode die Ausgabe einer bestimmten Ausführung nicht überprüfen.
Normalerweise verbringen Entwickler viel Zeit und Mühe damit, Testfälle zu schreiben, um sicherzustellen, dass die Anwendung wie erwartet funktioniert. Allerdings ist es auch wichtig, negative Testfälle zu testen. Negative Testfälle beziehen sich auf Testfälle, die testen, ob das System mit ungültigen Daten umgehen kann. Stellen Sie sich beispielsweise eine einfache Funktion vor, die einen vom Benutzer eingegebenen alphanumerischen Wert der Länge 8 liest. Zusätzlich zu alphanumerischen Werten sollten die folgenden negativen Testfälle getestet werden:
Benutzerspezifische nicht-alphanumerische Werte wie Sonderzeichen.
Benutzerdefinierter Nullwert.
Benutzerdefinierter Wert größer oder kleiner als 8 Zeichen.
Ähnlich testen Grenztestfälle, ob das System für Extremwerte geeignet ist. Wenn der Benutzer beispielsweise einen numerischen Wert von 1 bis 100 eingeben möchte, sind 1 und 100 die Grenzwerte und es ist sehr wichtig, das System auf diese Werte zu testen.
Das obige ist der detaillierte Inhalt von7 Tipps zum Schreiben von Java-Unit-Tests. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!