Skip to content

Latest commit

 

History

History
650 lines (469 loc) · 23.9 KB

File metadata and controls

650 lines (469 loc) · 23.9 KB

Testverfahren im Vergleich

1. Einleitung

1.1 Testverfahren im Vergleich

Es gibt unzählige Arten von Testverfahren. Viele Test sind in Ihrer Ausführung und im Testverhalten sehr ähnlich bzw. beschreiben faktisch die gleichen Testmethoden (z.B. Funktionentests, E2E-Tests, Browsertests, GUI-Tests, Akzeptanztest, Systemtests, etc.). Nachfolgend Testmethoden aus den Softwaretests, mit welchen man sich auseinandersetzen kann (natürlich nur ein "Auszug"):

1.1.1 funktionale Tests (funktionale Anforderungen)

1.1.2 Nichtfunktionale Tests (Funktionsweise eines Systems)

Die Namen vieler nichtfunktionaler Tests werden häufig austauschbar verwendet und überlappen oft stark.

1.2. Konzentration auf das Wesentliche

Oftmals ist es nicht notwendig (vor allem in der Webentwicklung) sich auf alle Testverfahren zu konzentrieren: Konzentriere dich nur auf die Wichtigsten! Viele nichtfunktionale Tests werdem durch Systemadministratoren ausgeführt (z.B. Lasttests), andere sind Tests für Spezialisten (z.B. Usability-Tests). Wichtiger sind eher Tests aus dem funktionalen Test-Bereich:

Unit-Tests (Komponententests) Integration-Tests Funktionstests (Akzeptanztests)
Kurzbe-
schreibung
Prüft kleine Code-Teile wie zum Beispiel individuelle Funktionen auf einen Erwartungswert bei gegebenen Eingaben. Prüft Unit-Tests in Systemumgebungen bzw. in Abhängigkeiten auf dem auszuführendem Systems (Datenbank, Dateisystem, Netzwerk, anderen Unit-Tests, etc.). Prüft, ob eine Software aus Sicht des Benutzers wie beabsichtigt funktioniert.
Eigen-
schaften
  • unabhängig vom System (DB, Dateisystem, Netzwerk, etc.) bzw. erfordern keine Kenntniss vom System
  • meist auf Funktionsebene
  • Definition Eingang und erwarteter Ausgang
  • Verringern die notwendige Anzahl an Akzeptanztests
  • meist systemabhängig (DB, Dateisystem, Netzwerk, andere Module, etc.)
  • helfen dabei die Zusammenarbeit von systemunabhängigen Komponenten (z.B. Unit-Tests) in einer systemabhängigen Umbegung zu prüfen
  • sind kostspieligerin der Ausführung als systemunabhängige Tests
  • Automatisierte Browsersimulation
  • Überprüfen von User-Aktionen
  • Einbeziehen von Umgebungseigenschaften (Ausführen von Javascript, etc.)
  • Durch Realisierung von User-Aktionen, teilweise sehr kostspielig in der Ausführung: Sparsames Einsetzen!
  • Für geschäftskritische Bereiche: Login, Warenkorb, Formulare, etc.
  • Sofern möglich: Auf Unit- oder Integration-Tests setzen
Entwicklungs-
ebene
Auf Code-Ebene (Programmiersprachenabhängig) In den meisten Fällen ebenfalls auf Code-Ebene (Programmiersprachenabhängig) Auf "Bedienebene" (unabhängig von der Entwicklungsumgebung und Sprache)
Geschrieben von Entwicklern Entwicklern Entwicklern, QA-Testern, Usern (Kunden)
Verwendet von Entwicklern, Administratoren (Deployment) Entwicklern, Administratoren (Deployment) Usern (Kunden), Administratoren (Deployment)



2. Unit-Tests (Komponententests)

Komponententests sind Softwaretestverfahren, bei dem einzelne Code-Teile auf ihre Einsatzfähigkeit getestet werden. Dabei werden meist bei gegebenen Eingabewerten bestimmte Ausgabewerte erwartet.

2.1 Einleitend

2.1.1 Hilfreiche Hinweise

  • Ein einzelnes Modul (Code-Teil) enthält im optimalen Fall nur eine bestimmte Aufgabe, auf welche der Unit-Test angesetzt wird (entkoppelter Code)
    • Notfalls sollte ein gekoppelter Code-Bereich mit mehreren Aufgaben in weitere Code-Teile unterteilt werden, da es sonst schwierig ist Komponententests dafür auszuführen
    • Das Schreiben von Modultests hilft es den Code "besser" zu verstehen, da dieser zur automatischen Entkopplung führt
  • Module sind immer unabhängig von der auszuführenden Umgebung (DB, Dateisystem, Netzwerk, etc.) und können somit problemlos auf jedem System erfolgreich durchgeführt werden
  • Komponententests sollten anderen kostspieligeren Tests vorgezogen werden, da sie meist nur eine Zeit im Millisekunden-Bereich benötigen
  • Komponententests erhöhen das Vertrauen von Änderungen am eigentlichen Quellcode eines Projektes (Code-Refactoring), da am Ende geprüft werden kann, dass alles wie zuvor funktioniert
  • Komponenten-Tests werden für gewöhnlich in der gleichen Programmiersprache wie das zu testende Projekt geschrieben

2.1.2 Vorraussetzungen, Anforderungen

  • Kommandozeilenorientiert: automatische Tests vor dem Deployment (aus Administratorsicht)
  • Schnelle und unkomplizierte Durchführung der Tests
    • sofort nach Änderungen
    • vor der Versionierung seiner Änderungen
    • vor Deployments

2.1.3 Unit-Tests mit ihren Testing-Frameworks sind...

  • systemunabhängig (Unabhängigkeit)
    • unabhängig von externen Faktoren wie DB, Dateisystem, Netzwerk, etc.
    • können isoliert ausgeführt werden
  • sehr schnell in der Ausführung (Schnelligkeit)
    • enthalten je mach Projektgröße meist mehrere Tausende Tests
  • ergebnisgleich (Wiederholbarkeit)
    • das Ausführen eines Komponententests sollte immer zu den gleichen Ergebnissen führen
  • "hinterfragend" (Verhältnismäßigkeit)
    • nimmt der Testaufbau wesentlich mehr Zeit in Anspruch als der zu testende Code-Teil, ist dieser Code-Teil wahrscheinlich zu komplex und bedarf einer Überarbeitung, welche besser getestet werden kann (Entkopplung)
  • lesbar (Lesbarkeit)
    • Komponententests sollten einfach zu lesen und warten sein
  • abhängig von der Programmiersprache seines Projektes

2.2 Übersicht

2.2.1 Unit Testing Frameworks

  • PHPUnit
  • Mocha
    • benötigt Kenntnisse in der Programmiersprache "Javascript"
    • ist ein Framework für Javascript-Entwickler
    • hat eine sehr schlanke Testing-Struktur
    • einfach zu installieren: user$ npm install mocha
  • etc.

2.3 Best Practices

Nachfolgend ein paar Best Practices zum Thema Komponententests.

2.3.1 Benennung der Tests

Die Benennung der Tests sollte die Absicht des Tests explizit ausdrücken. Folgende drei Teile haben sich als bewährt erachtet:

  1. der Name der zu testenden Methode
  2. die Bedingung unter der getestet wird
  3. erwartete Verhalten

Beispiel:

  • getDateFormated_shortTerm_returnsTextInSeconds
  • getDateFormated_mediumTerm_returnsTextInHours
  • getDateFormated_longTerm_returnsTextInDays

2.3.2 Testaufbau

Das Wichtigste beim Aufbau der Tests: Lesbarkeit! Vermeide Kurzschreibweisen und "Einzeiler". Folgender Aufbau eines einzelnen Tests hat sich als bewährt erachtet:

  1. Vorkehrung (z.B. Initialisierung von Objekten, etc.) (Arrange)
  2. Ausführung der Aktion am Objekt, Element, etc. (Act)
  3. Behauptung überprüfen (Assert)

Beispiel:

function FormatterGetText_shortTerm_returnsTextInSeconds() {

    /* Arrange */
    var formatter = new Formatter();
    var dateTextExpected = '20 seconds ago';
    
    /* Act */
    var dateTextFormated = formatter.getText([2019, 03, 12, 10, 00, 00], [2019, 03, 12, 10, 00, 20]);
    
    /* Assert */
    assert.equal(dateTextFormated, dateTextExpected);
}
describe('function concatWithSpace() {}', function() {
  describe('Two simple strings.', function() {
    it('Should return the concated string with a space between.', function() {

      /* Arrange */
      var stringExpected = 'Hello world';
      var stringWord1 = 'Hello';
      var stringWord2 = 'world';

      /* Act */
      var stringResult = concatWithSpace(stringWord1, stringWord2);

      /* Assert */
      assert.equal(stringResult, stringExpected);
    });
  });
});

2.3.3 Einfachheit der Tests (1/2)

  • Vermeide Dinge, die vom Test ablenken oder die Tests "überladen"
  • Vermeide Unnötiges und Wiederholungen (z.B. mehrfache Tests ähnlicher/gleicher Dinge)

2.3.4 Einfachkeit der Tests (2/2)

Tests sollten so gut es geht keine Logik enthalten. Zur Erinnerung: Ein Test enthält einen Eingabewert und den zu erwartenden Ausgabewert. Logische Bedingungen (if, while, for, switch, etc.) sollten vermieden werden.

2.3.5 Nur eine Assert-Anweisung pro Test

Mehrfache assert-Werte sind oftmals in vielen Test-Frameworks möglich. Ein Test sollte nur einen zu überprüfenden Wert enthalten (eine assert Anweisung). (siehe auch Einfachkeit der Tests 2/2)

2.3.6 Vermeiden "magischer" Zeichenfolgen

Der Test soll lesbar sein. Zu testende Werte werden in Variablen ausgelagert, um ein Ablenken auf spezielle Werte zu vermeiden.

Beispiel:

describe('function maxValue() {}', function() {
  describe('Test the maximum value.', function() {
    it('Should return the maximum value', function() {

      /* Arrange */
      var valueExpected = 65535;

      /* Act */
      var valueTested = maxValue(65535);

      /* Assert */
      assert.equal(valueTested, valueExpected);
    });
  });
});
describe('function maxValue() {}', function() {
  describe('Test the maximum value.', function() {
    it('Should return the maximum value', function() {

      /* Arrange */
      var valueExpected = 65535;

      /* Act */
      var valueTested = maxValue(65535);

      /* Assert */
      assert.equal(valueTested, valueExpected);
    });
  });
});

2.3.7 Vollständige Kontrolle von Ereignissen

Vermeide Funktionen wie now in Tests und verwende stattdessen gegebene kontrollierte Eingabewerte.

2.3.8 Teste nur öffentliche Methoden

Todo..



3. Integration-Tests

Todo..

3.1 Einleitend

Todo..

3.1.1 Hilfreiche Hinweise

Todo..

3.1.2 Vorraussetzungen, Anforderungen

  • Kommandozeilenorientiert: automatische Tests vor dem Deployment (aus Administratorsicht)

3.1.3 Integrationstests und deren Frameworks sind...

  • Todo..

3.2 Übersicht

3.2.1 Integration Testing Frameworks

  • Todo..

3.3 Best Practices

Todo..

3.3.1 Erster Punkt

Todo



4. Funktionstests (Akzeptanztests)

Nachfolgend der Testbereich, welcher die größte Schnittmenge zwischen Usern (Kunden) und Entwicklern bei der Testerstellung und der Testverwendung abdeckt: den Akzeptanztests respektive den E2E-Tests. Akzeptanztest erhöhen das Vertrauen in der Verwendung der Software zwischen Usern (Kunden) und den Entwicklern.

4.1 Einleitend

4.1.1 Hilfreiche Hinweise

  • Akzeptanztest sparsam einsetzen, da diese bedingt durch die Simulation von User-Aktionen in der Ausführung sehr kostspielig sind und dadurch eine gewisse Ausführungszeit benötigen
  • Wenn möglich, dann eher detailierte Prüfungen durch Unit- und Integrationstests abdecken!
    • Lade Datensatz xy (Eingaben) und erwarte Inhalt yz
  • Nur die entscheidenden (geschäftskritischen) Funktionen testen, z.B.:
    • Loginfunktion
    • Warenkorbfunktion
    • Bestellprozess
    • Formularfunktionen
    • eher nicht: jede einzelne Seite auf Inhalte überprüfen (mögliche Änderungen am Inhalt brechen den Test)

4.1.2 Vorraussetzungen, Anforderungen

  • visuelle Darstellung der Tests (aus Usersicht)
  • Kommandozeilenorientiert: automatische Tests vor dem Deployment (aus Administratorsicht)

4.1.3 Akzeptanztests und deren Frameworks sind...

  • unabhängig von der zu testenden Anwendung
  • unabhängig von der Programmiersprache der zu testenden Anwendung
  • unabhängig von den Entwicklungsfortschritten
  • werden parallel zu den oben aufgeführten Schritten durchgeführt
  • können von einer "Drittperson" durchgeführt werden

4.2 Übersicht

4.2.1 E2E Testing Frameworks

4.3 Best Practices

Nachfolgende genannte bewährte Verfahren beziehen sich auf alle funktionalen Testverfahren (Akzeptanztest) und sind Framework- und Tool-unabhängig. Genannte Codebeispiele beziehen sich auf auf das Cypress Framework.

4.3.1 Login

  • Logge dich nicht über die GUI ein (langsam und durchaus fehleranfällig)
  • Logge dich programmgesteuert in deine Anwendung ein
    • übernimm die Kontrolle deiner Anwendung: Setze den Login-Status direkt beim Testen
  • Einzige Ausnahme: Du testest den Login-Prozess direkt

4.3.2 Verwenden von Selektoren

  • Verwende keine Design-gebundenen Selektoren (CSS, classes, Tags, etc.)
    • diese können sich ändern und brechen damit den Test
  • Verwende Design-ungebundene Selektoren (data Attribute, Text, etc.)
    • Diese werden nur für das Testing verwendet
    • Text-Tests sind wichtig, wenn der Test abhängig vom Text ist (Speichern vs. Abbrechen)

Beispiel:

<button id="main-button" class="btn btn-save" data-cy="save">Save</button>

  • Niemals (kein Kontext): cy.get('button').click()
  • Auch nicht (kann sich bei Designänderungen ändern): cy.get('.btn.btn-save').click()
  • Besser (nicht eindeutig ersichtlich, dass diese ID zum testen verwendet wird): cy.get('#main-button').click()
  • OK (nur wenn Textänderungen den Test brechen sollen): cy.contains('Save').click()
    • z.B. Text ändert sich von "Save" zu "Cancel"
  • Optimal (eindeutig ersichtlich, dass dieses Attribut zum Testen verwendet wird): cy.get('[data-cy=save]').click()

4.3.3 Besuch von externen Quellen (Seiten, APIs, etc.)

  • Interagiere mit keinen Websites oder Servern, die du nicht selbst kontrollierst
  • Besser: verwende gar keine externen Quellen
  • Teste nur, was Du auch kontrollieren kannst
  • Versuche zu vermeiden, dass du einen Server eines Drittanbieters benötigst

Eventuelle Ausnahmen:

  • Testen einer Anmeldung, welche einen anderen Anbieter nutzt
  • Datenänderungen zu einem Dritt-Dienst müssen überprüft werden
  • Testen der "Passwort vergessen" Funktion
  • etc.

Hintergrund:

  • Testen von Drittdiensten ist unglaublich zeitaufwendig und verlangsamt Deine Tests
  • Die Website von Drittanbietern kann ihren Inhalt ändern oder aktualisieren (außerhalb deiner Kontrolle)
  • Externe Quellen können Probleme haben (500er Fehler, etc.)
  • Der Drittanbieter kann erkennen, dass du über ein Skript testest und Dich blockieren
  • Die Drittanbieter-Website kann AB-Kampagnen durchführen (Unterschiedliche Erwartungswerte)
  • Externe Quellen benötigen eine Internet Verbindung (eine fehlende Verbindung bricht den Test)

Tipps und Tricks für den Login-Prozess:

  • Lokaler "Fake-Login": Erwartete Login-Rückgabewert kommt aus deiner Anwendung selbst und wird nicht an den Drittdienst gesendet (wenn möglich)
  • Wenn Du einen echten Token benötigst, kann dieser unter Umständen auch anders als über den Login erhalten werden (via API, fester Programmier-Token, etc.)
  • etc.

4.3.4 Testabhängigkeiten

  • Vermeide die Durchführung von Tests, welche auf dem Stand früherer Tests basieren
  • Tests sollten immer unabhängig voneinander ausgeführt werden können und trotzdem erfolgreich sein
  • Lagere geteilten Testcode aus, welcher von verschiedenen Tests benötigt wird (Initialisierungen von Formularen, etc.)

4.3.5 Komplexität von Tests

  • Einzelne Tests sollten nicht zu komplex, aber auch nicht zu einfach sein
  • Ein einzelner Test welcher die Webseite komplett testet ist genauso ineffektiv, wie viele Test, welche sich nur auf einen einzelnen Punkt konzentrieren (Seitenelement enthält den Wert xy, etc.)
  • Fasse sinnvolle Test-Elemente zu einem einzelnen Test zusammen
  • Zuviele Tests mit nur einem einzelnen Testelement sind nicht performant
  • Zu komplexe Tests schwierig zu warten

4.3.6 Unnötiges Warten

  • Vermeide zeitabhängige Tests (z.B. bis Element auf Seite zu sehen ist)
  • Meist gibt es einen einacheren Weg solche Dinge zu testen (eventuell sind sie gar nicht notwendig oder sogar falsch)

Beispiele:

Todo..

4.3.7 Web Server

  • Webserver (oder andere notwendige Dienste) sollten nicht innerhalb von Tests erst gestartet werden müssen
  • Besser: Die benötigten Dienste sind schon vor dem Teststart vorhanden (andernfalls wird der Test gar nicht erst gestartet)
  • Gestartete Dienste innerhalb des Tests belegen unnötig Ressourcen
  • Die Unabhängigkeit von Tests ist durchaus nicht gewährleistet (oder jeder Test müsste seinen eigenen Dienst starten)
  • Parallele Tests sind nicht möglich (Eventuelle Portkonflikte, etc.)

4.3.8 Verwenden von URLs

  • Vermeide die Verwendung von festen kompletten Adressen wie https://www.address.com/login
  • Verwende relative Adressen wie /login
  • Tests auf unterschiedlichen Umgebungen wird damit ermöglicht (Browser, Kommandozeile, etc.)
  • Verwende die allgemeine, umgebungsbasierte Konfiguration deines Frameworks um eine BaseURL zu hinterlegen
  • Tests sollten unabhängig der verwendeten URL sein (http://localhost, https://www.address.com, etc.)
  • Unabhängig von z.B. verwendeten Ports (http://localhost, http://localhost:8080, etc.)

4.4 Best Practices (Cypress)

4.4.1 Zuweisen von Rückgabewerten

Todo..

4.4.2 Verwendung von "after" oder "afterEach" Hooks

Todo..



A. Weitere Anleitungen

  • Todo..



B. Quellen



C. Tools

  • Todo..



D. Fußnoten

1 Der Akzeptanztest ist meist der Test, welchen man umgangssprächlich mit _Functional Testing_ meint



E. Autoren



F. Lizenz

Dieses Tutorial steht unter der MIT-Lizenz - siehe die Datei LICENSE.md für weitere Informationen.