Heim  >  Artikel  >  Java  >  7 Tipps zum Testen von Java-Einheiten

7 Tipps zum Testen von Java-Einheiten

怪我咯
怪我咯Original
2017-04-05 16:14:12944Durchsuche

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.

Obwohl 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.

7 Tipps zum Testen von Java-Einheiten

Arten von Softwareentwicklungstests

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

Java bietet mehrere Frameworks für Unit-Tests. TestNG und JUnit sind die beliebtesten Test-Frameworks. Einige wichtige Funktionen von JUnit und TestNG:

  • Einfach einzurichten und auszuführen.

  • Unterstützt Kommentare.

  • Ermöglicht das Ignorieren oder Gruppieren bestimmter Tests und die gemeinsame Ausführung.

  • unterstützt parametrisiertes Testen, d. h. das Ausführen von Unit-Tests durch Angabe verschiedener Werte zur Laufzeit.

  • Unterstützt die automatisierte Testausführung durch Integration mit Build-Tools wie Ant, Maven und Gradle.

EasyMock ist ein Mocking-Framework, das Unit-Testing-Frameworks wie JUnit und TestNG ergänzt. EasyMock selbst ist kein vollständiges Framework. Es bietet lediglich die Möglichkeit, Scheinobjekte zum einfacheren Testen zu erstellen. Beispielsweise kann eine Methode, die wir testen möchten, eine DAO-Klasse aufrufen, die Daten aus der Datenbank abruft. In diesem Fall kann EasyMock verwendet werden, um ein MockDAO zu erstellen, das hartcodierte Daten zurückgibt. Dadurch können wir unsere beabsichtigten Methoden einfach testen, ohne uns um den Datenbankzugriff kümmern zu müssen.

2. Verwenden Sie testgetriebene Entwicklung mit Vorsicht!

Test Driven Development (TDD) ist ein Softwareentwicklungsprozess, bei dem wir Tests basierend auf Anforderungen schreiben, bevor wir mit der Codierung beginnen. Da noch keine Codierung vorliegt, wird der Test zunächst fehlschlagen. Schreiben Sie dann die Mindestmenge an Code, um den Test zu bestehen. Anschließend den Code umgestalten, bis er optimiert ist.

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 modularem Code 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

Die Codeabdeckung misst (ausgedrückt als Prozentsatz) die Menge an Code, die ausgeführt wird, wenn Komponententests ausgeführt werden. Im Allgemeinen ist die Wahrscheinlichkeit, dass Code mit hoher Abdeckung unentdeckte Fehler enthält, geringer, da während des Tests ein größerer Teil seines Quellcodes ausgeführt wird. Zu den Best Practices zum Messen der Codeabdeckung gehören:

  • Verwenden Sie ein Codeabdeckungstool wie Clover, Corbetura, JaCoCo oder Sonar. Der Einsatz von Tools kann die Qualität Ihrer Tests verbessern, da sie auf Bereiche Ihres Codes hinweisen können, die nicht getestet werden, sodass Sie zusätzliche Tests entwickeln können, um diese Bereiche abzudecken.

  • Wenn neue Funktionen geschrieben werden, schreiben Sie sofort eine neue Testabdeckung.

  • 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 folgende Concat-Methode 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 die obige Methode Testfall für:

@Test
public void testStringUtil() {
     String result = stringUtil.concat(true, "Hello ", "World");
     System.out.println("Result is "+result);
}

In diesem Fall ist der Wert des ausgeführten Tests wahr. Wenn der Test ausgeführt wird, ist er erfolgreich. Wenn das Codeabdeckungstool ausgeführt wird, wird eine Codeabdeckung von 100 % angezeigt, da der gesamte Code in der Concat-Methode ausgeführt wurde. Wenn der Test jedoch mit dem Wert „false“ ausgeführt wird, wird eine 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.

4. Testdaten so weit wie möglich externalisieren

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 prü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));
        }
    }

TestNG

Das Folgende ist testng.xml (die Konfigurationsdatei für TestNG), die die Daten enthält, für die die Tests ausgefü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 in diesem Fall zu sehen ist Die Tests werden 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.

JUnit

Ä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 die Daten aus einer externen Datei zu lesen, anstatt sie fest zu codieren.

5. Verwenden Sie Assertionen anstelle von Print-Anweisungen

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 ausgegebene 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 StringUti-Klasse unten 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;
        }
    }

Im Folgenden sind zwei der oben genannten Methoden aufgeführt Unit-Tests:

@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 wird immer bestanden, da es keine Behauptungen hat. 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.

6. Erstellen Sie Tests mit deterministischen Ergebnissen

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 Jedes Mal, wenn die Methode „calcuteTime“ ausgeführt wird, wird 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.

7. Testen Sie zusätzlich zu positiven Szenarien auch negative Szenarien und Randfälle.

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 z. B. 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 Testen von Java-Einheiten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn