Was sind Tests?

Wenn Sie Software schreiben, können Sie durch Tests überprüfen, ob sie richtig funktioniert. Tests können allgemein als Prozess bezeichnet werden, bei dem Software auf bestimmte Weise ausgeführt wird, um sicherzustellen, dass sie sich wie vorgesehen verhält.

Erfolgreiche Tests können die Gewissheit geben, dass die bereits geschriebene Software weiterhin wie erwartet funktioniert, wenn Sie neuen Code oder neue Features hinzufügen oder sogar die Abhängigkeiten aktualisieren. Tests können auch zum Schutz Ihrer Software vor unwahrscheinlichen Szenarien oder unerwarteten Eingaben beitragen.

Beispiele für Verhaltensweisen im Web, die Sie testen können:

  • Sicherstellen, dass die Funktion einer Website korrekt funktioniert, wenn auf eine Schaltfläche geklickt wird
  • Bestätigen, dass eine komplexe Funktion die richtigen Ergebnisse liefert.
  • Ausführen einer Aktion, für die eine Nutzeranmeldung erforderlich ist
  • Überprüfen, ob bei der Eingabe fehlerhafter Daten in einem Formular ein Fehler gemeldet wird
  • Dafür sorgen, dass eine komplexe Webanwendung weiter funktioniert, wenn ein Nutzer eine extrem niedrige Bandbreite hat oder offline geht.

Automatisierte und manuelle Tests im Vergleich

Sie können Ihre Software auf zwei allgemeine Arten testen: automatische und manuelle Tests.

Bei manuellen Tests führen Menschen Software direkt aus, beispielsweise indem sie eine Website in ihrem Browser laden und prüfen, ob sie sich erwartungsgemäß verhält. Manuelle Tests lassen sich einfach erstellen oder definieren – beispielsweise, ob Ihre Website geladen werden kann? Können Sie diese Aktionen ausführen? Aber jeder Durchlauf kostet enorm viel Zeit von Menschen. Menschen sind zwar sehr kreativ, was eine Art von Tests ermöglichen kann, die als explorative Tests bezeichnet werden, aber wir können Fehler oder Inkonsistenzen immer noch schlecht erkennen, insbesondere wenn dieselbe Aufgabe mehrmals ausgeführt wird.

Automatisierte Tests sind ein Verfahren, mit dem Tests kodifiziert und von einem Computer wiederholt ausgeführt werden können, um das beabsichtigte Verhalten der Software zu bestätigen, ohne dass eine Person Schritte wie die Einrichtung oder Überprüfung der Ergebnisse wiederholt ausführen muss. Wichtig: Sobald automatisierte Tests konfiguriert sind, können sie häufig ausgeführt werden. Dies ist immer noch eine sehr breite Definition und Sie sollten beachten, dass automatisierte Tests alle möglichen Formen und Formen annehmen. Der Großteil dieses Kurses befasst sich mit automatischen Tests.

Manuelle Tests haben ihren Platz, oft als Vorläufer des Schreibens automatisierter Tests, aber auch dann, wenn automatisierte Tests zu unzuverlässig, zu breit angelegt oder umständlich beim Schreiben werden.

Die Grundlagen anhand eines Beispiels

Für uns als Webentwickler, die JavaScript oder ähnliche Sprachen schreiben, könnte ein prägnanter automatisierter Test ein Skript wie dieses sein, das Sie jeden Tag ausführen, z. B. über Node.de oder in einem Browser:

import { fibonacci } from "../src/math.js";

if (fibonacci(0) !== 0) {
  throw new Error("Invalid 0th fibonacci result");
}
const fib13 = fibonacci(13);
if (fib13 !== 233) {
  throw new Error("Invalid 13th fibonacci result, was=${fib13} wanted=233");
}

Dies ist ein vereinfachtes Beispiel, das die folgenden Erkenntnisse liefert:

  • Dies ist ein Test, da er Software (die Fibonacci-Funktion) ausführt und sicherstellt, dass sein Verhalten wie beabsichtigt funktioniert, indem die Ergebnisse mit erwarteten Werten verglichen werden. Wenn das Verhalten nicht korrekt ist, verursacht es einen Fehler, der durch JavaScript ausgedrückt wird, indem ein Error ausgelöst wird.

  • Auch wenn Sie dieses Skript manuell in Ihrem Terminal oder in einem Browser ausführen, ist dies ein automatisierter Test, da er wiederholt ausgeführt werden kann, ohne dass Sie einzelne Schritte ausführen müssen. Auf der nächsten Seite, wo Tests ausgeführt werden, wird näher erklärt.

  • Auch wenn bei diesem Test keine Bibliotheken verwendet werden, sondern JavaScript, das überall ausgeführt werden kann, ist er dennoch ein Test. Es gibt viele Tools, die Ihnen beim Schreiben von Tests helfen können. Dazu gehören auch solche, die später in diesem Kurs behandelt werden. Sie arbeiten jedoch alle nach dem grundlegenden Prinzip, Fehler zu verursachen, wenn etwas schiefgeht.

Bibliotheken in der Praxis testen

Die meisten Bibliotheken oder integrierten Test-Frameworks bieten zwei wichtige Primitive, die das Schreiben von Tests vereinfachen: Assertions und eine Möglichkeit, unabhängige Tests zu definieren. Diese werden im Abschnitt Assertions und andere Primitive ausführlich behandelt. Auf übergeordneter Ebene sollten Sie jedoch bedenken, dass fast alle Tests, die Sie sehen oder schreiben, am Ende diese Art von Primitiven verwenden.

Assertions sind eine Möglichkeit, die Prüfung eines Ergebnisses und die Ausgabe eines Fehlers zu kombinieren, wenn ein Fehler auftritt. Sie können den vorherigen Test beispielsweise prägnanter machen, indem Sie assert einführen:

import { fibonacci } from "../src/math.js";
import { assert } from "a-made-up-testing-library";

assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");

Sie können diesen Test weiter verbessern, indem Sie unabhängige Tests definieren, die optional in Suites gruppiert werden können. Die folgende Suite testet die Fibonacci-Funktion und die katalanische Funktion unabhängig voneinander:

import { fibonacci, catalan } from "../src/math.js";
import { assert, test, suite } from "a-made-up-testing-library";

suite("math tests", () => {
  test("fibonacci function", () => {
    assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
    assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");
  });
  test("relationship between sequences", () => {
    const numberToCheck = 4;
    const fib = fibonacci(numberToCheck);
    const cat = catalan(numberToCheck);
    assert.isAbove(fib, cat);
  });
});

In diesem Kontext von Softwaretests bezieht sich Test als Substantiv auf einen Testfall: ein einzelnes, unabhängiges, adressierbares Szenario wie der Testfall "Beziehung zwischen Sequenzen" im vorherigen Beispiel.

Einzeln benannte Tests sind unter anderem für die folgenden Aufgaben nützlich:

  • Bestimmen, wie ein Test im Laufe der Zeit erfolgreich ist oder fehlschlägt.
  • Hervorheben eines Fehlers oder Szenarios, damit Sie leichter testen können, ob das Szenario behoben ist.
  • Einige Tests unabhängig von anderen ausführen, z. B. mithilfe eines glob-Filters.

Stellen Sie sich Testfälle mit den drei „A“s von Unittests vor: arrange (ordnen, handeln, bestätigen). Für jeden Testlauf gilt im Wesentlichen Folgendes:

  • Ordnen Sie einige Werte oder Status an (dies können nur hartcodierte Eingabedaten sein).
  • Ausführen einer Aktion, z. B. dem Aufrufen einer Methode
  • Übernehmen Sie die Ausgabewerte oder den aktualisierten Status (mit assert).

Der Umfang der Tests

Die Codebeispiele im vorherigen Abschnitt beschreiben einen Einheitentest, da sie kleinere Teile Ihrer Software testen und sich oft auf eine einzelne Datei und in diesem Fall nur auf die Ausgabe einer einzelnen Funktion konzentrieren. Die Testkomplexität wächst mit dem Code aus mehreren Dateien, Komponenten oder sogar verschiedenen miteinander verbundenen Systemen (manchmal außerhalb Ihrer Kontrolle, z. B. ein Netzwerkdienst oder das Verhalten einer externen Abhängigkeit). Aus diesem Grund werden Testtypen häufig nach ihrem Bereich oder ihrer Skalierung benannt.

Neben Unittests sind unter anderem Komponententests, visuelle Tests und Integrationstests aufgeführt. Keiner dieser Namen hat detaillierte Definitionen und können je nach Codebasis unterschiedliche Bedeutungen haben. Verwenden Sie sie daher als Orientierung und überlegen Sie sich Definitionen, die für Sie geeignet sind. Was ist beispielsweise eine Komponente, die in Ihrem System getestet wird? Für React-Entwickler kann dies buchstäblich einer „React-Komponente“ zugeordnet werden, hat aber für Entwickler in anderen Kontexten möglicherweise eine andere Bedeutung.

Der Umfang eines einzelnen Tests kann ihn in ein Konzept einordnen, das oft als „Testpyramide“ bezeichnet wird. Dies kann eine gute Faustregel dafür sein, was ein Test prüft und wie er läuft.

Die Testpyramide mit End-to-End-Tests (E2E) oben, Integrationstests in der Mitte und Einheitentests unten.
Die Testpyramide.

Diese Idee wurde überarbeitet und verschiedene andere Formen sind mittlerweile beliebt, z. B. der Testdiamant oder der Testeiskegel. Ihre Prioritäten beim Schreiben von Tests sind wahrscheinlich für Ihre Codebasis einzigartig. Eine gängige Funktion ist jedoch, dass einfachere Tests wie Einheitentests tendenziell schneller ausgeführt und einfacher zu schreiben sind (damit Sie mehr davon haben) und dass Tests in einem begrenzten Umfang durchgeführt werden. Komplexe Tests wie End-to-End-Tests sind hingegen schwer zu schreiben, können aber einen größeren Umfang testen. Die oberste Ebene vieler Testformen besteht in der Regel aus manuellen Tests, da einige Nutzerinteraktionen zu komplex sind, um sie in einen automatisierten Test zu codieren.

Diese Typen werden in den Arten automatischer Tests erweitert.

Wissen testen

Welche Primitive bieten die meisten Testbibliotheken und -Frameworks?

Ein Runner-Dienst, der einen Cloud-Anbieter verwendet.
Einige browserbasierte Runner bieten eine Möglichkeit, Tests auszulagern. Dies ist jedoch keine normale Funktion von Testbibliotheken.
Assertions, die Ausnahmen verursachen, wenn sie nicht erfüllt werden
Sie können zwar einen Fehler ausgeben, um einen Test nicht zu bestehen, aber assert() und seine Varianten werden häufig eingeschlossen, da sie das Schreiben von Prüfungen erleichtern.
Möglichkeit, Tests in der Testpyramide zu kategorisieren
Es gibt dafür keine Standardmethode. Sie können den Namen Ihrer Tests auch ein Präfix hinzufügen oder sie in verschiedenen Dateien platzieren. Die Kategorisierung ist jedoch in den meisten Test-Frameworks nicht integriert.
Die Fähigkeit, unabhängige Tests nach Funktion zu definieren.
Die Methode test() ist in fast allen Test-Runnern enthalten. Da Testcode nicht auf der obersten Ebene einer Datei ausgeführt wird, kann der Test-Runner jeden Testlauf als unabhängige Einheit behandeln.