Java Language
Unit Testing
Suche…
Einführung
Bemerkungen
Unit Test Frameworks
Es gibt zahlreiche Frameworks für Unit-Tests in Java. Die mit Abstand beliebteste Option ist JUnit. Es ist unter folgendem dokumentiert:
JUnit4 - Vorgeschlagenes Tag für JUnit4-Funktionen; noch nicht implementiert
Andere Unit-Test-Frameworks sind vorhanden und verfügen über Dokumentation:
Testgeräte für Einheiten
Es gibt mehrere andere Tools, die für die Prüfung von Einheiten verwendet werden:
Mockito - Spottendes Gerüst; ermöglicht das Nachahmen von Objekten. Nützlich, um das erwartete Verhalten einer externen Einheit im Test einer bestimmten Einheit nachzuahmen, um das Verhalten der externen Einheit nicht mit den Tests der jeweiligen Einheit zu verknüpfen.
JBehave - BDD Framework. Ermöglicht die Verknüpfung von Tests mit dem Benutzerverhalten (Validierung der Anforderungen / Szenarien). Zum Zeitpunkt des Schreibens ist kein Dokument verfügbar. Hier ist ein externer Link .
Was ist Unit Testing?
Dies ist ein bisschen eine Grundierung. Es wird meistens eingesetzt, weil die Dokumentation ein Beispiel haben muss, auch wenn sie als Stub-Artikel gedacht ist. Wenn Sie bereits Grundlagen zum Testen von Einheiten kennen, können Sie mit den Anmerkungen fortfahren, in denen bestimmte Rahmenbedingungen erwähnt werden.
Unit-Tests stellen sicher, dass sich ein bestimmtes Modul wie erwartet verhält. In großen Anwendungen ist die Gewährleistung der ordnungsgemäßen Ausführung von Modulen im Vakuum ein wesentlicher Bestandteil der Gewährleistung der Anwendungstreue.
Betrachten Sie das folgende (triviale) Pseudo-Beispiel:
public class Example {
public static void main (String args[]) {
new Example();
}
// Application-level test.
public Example() {
Consumer c = new Consumer();
System.out.println("VALUE = " + c.getVal());
}
// Your Module.
class Consumer {
private Capitalizer c;
public Consumer() {
c = new Capitalizer();
}
public String getVal() {
return c.getVal();
}
}
// Another team's module.
class Capitalizer {
private DataReader dr;
public Capitalizer() {
dr = new DataReader();
}
public String getVal() {
return dr.readVal().toUpperCase();
}
}
// Another team's module.
class DataReader {
public String readVal() {
// Refers to a file somewhere in your application deployment, or
// perhaps retrieved over a deployment-specific network.
File f;
String s = "data";
// ... Read data from f into s ...
return s;
}
}
}
Dieses Beispiel ist also trivial; DataReader
erhält die Daten aus einer Datei und leitet sie an den Capitalizer
. Dieser konvertiert alle Zeichen in Großbuchstaben, die dann an den Consumer
. Der DataReader
ist jedoch eng mit unserer Anwendungsumgebung verbunden. DataReader
verschieben wir den Test dieser Kette, bis wir bereit sind, eine Testversion bereitzustellen.
getVal()
, irgendwo auf dem Weg in einer Version wurde aus unbekannten Gründen die getVal()
Methode in Capitalizer
geändert, toUpperCase()
ein toUpperCase()
String in einen toLowerCase()
String zurückgegeben wurde:
// Another team's module.
class Capitalizer {
...
public String getVal() {
return dr.readVal().toLowerCase();
}
}
Dies bricht eindeutig das erwartete Verhalten. Aufgrund der mühsamen Prozesse, die mit der Ausführung des DataReader
, werden wir dies erst bei unserer nächsten DataReader
feststellen. Tage / Wochen / Monate vergehen also mit diesem Fehler in unserem System, und der Produktmanager sieht dies und wendet sich sofort an Sie, den Teamleiter, der mit dem Consumer
. "Warum passiert das? Was hast du geändert?" Offensichtlich bist du ahnungslos. Du hast keine Ahnung, was los ist. Sie haben keinen Code geändert, der dies berühren sollte. Warum ist es plötzlich kaputt?
Nach einer Diskussion zwischen den Teams und der Zusammenarbeit wird das Problem verfolgt und das Problem gelöst. Aber es wirft die Frage auf; Wie konnte das verhindert werden?
Es gibt zwei offensichtliche Dinge:
Tests müssen automatisiert werden
Unser Vertrauen auf manuelle Tests ließ diesen Fehler viel zu lange unbemerkt bleiben. Wir brauchen eine Möglichkeit, den Prozess zu automatisieren, bei dem Fehler sofort eingeführt werden. Keine 5 Wochen mehr. Nicht in 5 Tagen. Keine 5 Minuten von jetzt an. Jetzt sofort.
Sie müssen wissen, dass ich in diesem Beispiel einen sehr banalen Fehler zum Ausdruck gebracht habe, der eingeführt und nicht bemerkt wurde. In einer industriellen Anwendung, bei der Dutzende von Modulen ständig aktualisiert werden, können sich diese überall einschleichen. Sie fixieren etwas mit einem Modul, nur um zu erkennen, dass das Verhalten, auf das Sie "repariert" haben, auf eine andere Weise (intern oder extern) verwendet wurde.
Ohne strenge Validierung schleichen sich die Dinge in das System ein. Es ist möglich, dass, wenn man es weit genug vernachlässigt, dies so viel zusätzlichen Aufwand mit sich bringt, Änderungen zu korrigieren (und diese Fixes dann zu korrigieren usw.), dass ein Produkt tatsächlich mit der verbleibenden Arbeit zunimmt, je mehr Aufwand vorhanden ist. Sie möchten nicht in dieser Situation sein.
Tests müssen feinkörnig sein
Das zweite Problem, das in unserem obigen Beispiel festgestellt wurde, ist die Zeit, die zum Aufspüren des Fehlers benötigt wurde. Der Produktmanager hat Sie mit einem Ping versehen, als die Tester dies bemerkten, Sie haben untersucht und festgestellt, dass der Capitalizer
scheinbar schlechte Daten zurückgibt, Sie haben das Capitalizer
Team mit Ihren Befunden, Ihren Ermittlungen usw. etc. angerufen.
Der gleiche Punkt, den ich oben bezüglich der Menge und Schwierigkeit dieses trivialen Beispiels angesprochen habe, trifft hier zu. Natürlich kann jeder, der mit Java einigermaßen vertraut ist, das eingeführte Problem schnell finden. Es ist jedoch oft viel schwieriger, Probleme aufzuspüren und zu kommunizieren. Vielleicht hat Ihnen das Capitalizer
Team einen JAR ohne Quelle zur Verfügung gestellt. Möglicherweise befinden sie sich auf der anderen Seite der Welt und die Kommunikationszeiten sind sehr begrenzt (z. B. für E-Mails, die einmal täglich gesendet werden). Es kann zu Fehlern führen, die Wochen oder länger dauern, bis sie aufgespürt wurden (und wiederum kann es mehrere davon für eine bestimmte Version geben).
Um dem entgegenzuwirken, wünschen wir uns strenge Tests auf einem möglichst feinen Niveau (Sie wollen auch grobkörnige Tests, um sicherzustellen, dass die Module ordnungsgemäß zusammenwirken, aber das ist hier nicht unser Schwerpunkt). Wir möchten genau festlegen, wie alle nach außen gerichteten Funktionen (mindestens) funktionieren, und Tests für diese Funktionen durchführen.
Komponententest eingeben
Stellen Sie sich vor, wir hätten einen Test, der insbesondere sicherstellt, dass die getVal()
Methode von Capitalizer
eine getVal()
für eine bestimmte Eingabezeichenfolge getVal()
. Stellen Sie sich außerdem vor, dass der Test ausgeführt wurde, bevor wir überhaupt Code geschrieben haben. Der Fehler in das System eingeführt (das heißt, toUpperCase()
mit ersetzt toLowerCase()
) würde keine Probleme verursachen , weil der Fehler würde nie in das System eingebracht werden. Wir würden es in einem Test fangen, der Entwickler würde (hoffentlich) ihren Fehler erkennen und eine alternative Lösung finden, wie die beabsichtigte Wirkung eingeführt werden kann.
Es wurden einige Auslassungen gemacht, wie diese Tests implementiert werden können, aber diese werden in der Framework-spezifischen Dokumentation (verlinkt in den Anmerkungen) behandelt. Hoffentlich dient dies als Beispiel dafür, warum Unit-Tests so wichtig sind.