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)
- Akzeptanztest oder auch UAT1
- API Tests oder auch API testing
- Integrationstest oder auch integration testing
- Interface Testing
- Modultests, Komponententests oder auch unit testing
- Rauchtest oder auch smoke testing
- Regression Testing oder auch regression testing
- Sanity testing
- Systemtests oder auch system testing
1.1.2 Nichtfunktionale Tests (Funktionsweise eines Systems)
Die Namen vieler nichtfunktionaler Tests werden häufig austauschbar verwendet und überlappen oft stark.
- A/B Tests
- Availability testing
- Baseline testing
- Compatibility testing
- Compliance testing
- Configuration testing
- Conformance testing
- Documentation testing
- Endurance testing oder auch soak testing
- Ergonomics testing
- Interoperability testing
- Installation testing
- Lasttest oder auch load testing
- Localization testing and Internationalization testing
- Maintainability testing
- Operational readiness testing
- Penetrationstest oder auch pentest
- Performance testing
- Recovery testing
- Reliability testing
- Resilience testing
- Security testing
- Scalability testing
- Stresstest oder auch stress testing
- Usability-Test oder auch usability testing
- Volume testing
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 |
|
|
|
|
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) |
Komponententests sind Softwaretestverfahren, bei dem einzelne Code-Teile auf ihre Einsatzfähigkeit getestet werden. Dabei werden meist bei gegebenen Eingabewerten bestimmte Ausgabewerte erwartet.
- 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
- 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
- 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
- PHPUnit
- benötigt Kenntnisse in der Programmiersprache "PHP"
- ist ein Framework für PHP-Entwickler
- hat eine sehr schlanke Testing-Struktur
- meist ein Order mit den verschiedenen "Test-Gruppen": https://github.com/bjoern-hempel/php-web-crawler/tree/master/tests
- z.B. über Composer zu installieren:
user$ composer require --dev phpunit/phpunit ^8
- 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.
Nachfolgend ein paar Best Practices zum Thema Komponententests.
Die Benennung der Tests sollte die Absicht des Tests explizit ausdrücken. Folgende drei Teile haben sich als bewährt erachtet:
- der Name der zu testenden Methode
- die Bedingung unter der getestet wird
- erwartete Verhalten
Beispiel:
getDateFormated_shortTerm_returnsTextInSecondsgetDateFormated_mediumTerm_returnsTextInHoursgetDateFormated_longTerm_returnsTextInDays
Das Wichtigste beim Aufbau der Tests: Lesbarkeit! Vermeide Kurzschreibweisen und "Einzeiler". Folgender Aufbau eines einzelnen Tests hat sich als bewährt erachtet:
- Vorkehrung (z.B. Initialisierung von Objekten, etc.) (Arrange)
- Ausführung der Aktion am Objekt, Element, etc. (Act)
- 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);
});
});
});- Vermeide Dinge, die vom Test ablenken oder die Tests "überladen"
- Vermeide Unnötiges und Wiederholungen (z.B. mehrfache Tests ähnlicher/gleicher Dinge)
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.
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)
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);
});
});
});Vermeide Funktionen wie now in Tests und verwende stattdessen gegebene kontrollierte Eingabewerte.
Todo..
Todo..
Todo..
Todo..
- Kommandozeilenorientiert: automatische Tests vor dem Deployment (aus Administratorsicht)
- Todo..
- Todo..
Todo..
Todo
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.
- 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)
- visuelle Darstellung der Tests (aus Usersicht)
- Kommandozeilenorientiert: automatische Tests vor dem Deployment (aus Administratorsicht)
- 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
- Selenium WebDriver
- benötigt Kenntnisse in der Programmiersprache "Java"
- ist ein Framework für QA-Entwickler
- erfordert eine komplexe Umgebung (besitzt ein "Framework Architecture Design")
- Cypress
- etc.
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.
- 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
- 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()
- 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.
- 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.)
- 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
- 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..
- 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.)
- 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.)
Todo..
Todo..
- Todo..
- Allgemein
- Komponententests
- Cypress
- Todo..
| 1 | Der Akzeptanztest ist meist der Test, welchen man umgangssprächlich mit _Functional Testing_ meint |
- Björn Hempel bjoern@hempel.li - Erste Arbeiten - https://github.com/bjoern-hempel
Dieses Tutorial steht unter der MIT-Lizenz - siehe die Datei LICENSE.md für weitere Informationen.
