Automatisiserte Softwaretests und Testabdeckung

Ein oft bemütes Kriterum zum Einschätzen der Qualität einer Software ist ihre sogenannte Testabdeckung. Als Testabdeckung bezeichnet man den Prozentsatz eines Programmes (bzw. seines Quellcodes), der von automatisierten Test ausgeführt wird.

Auch wir bei Webrunners setzen schon seit vielen Jahren automatisierte Softwaretests beim Entwickeln unserer Software und Webanwendungen ein.

Dieser Artikel soll fachfremden interessierten Lesern einen kurzen Überblick darüber geben was automatisierte Tests sind, wozu sie eingesetzt werden und warum die Testabdeckung kein hinreichendes Kriterium, für zufriedenstellend implementierte Tests, ist.

Was ist überhaupt ein automatisierter Softwaretest?

Fangen wir ganz am Anfang an: Was ist überhaupt ein automatisierter Softwaretest?

Im Prinzip wird in ihm festgelegt, welches Ergebnis eine Software oder dessen Programmcode korrekterweise zurückliefern soll. Dabei handelt es sich bei so einem Test selbst um ein Softwareprogramm, also eine Folge von Anweisungen, die mit der eigentlich zu testenden Software interagiert. Dies kann auf verschiedenen Ebenen erfolgen, weswegen man verschiedene Formen von Softwaretests unterscheidet. Häufig verwendete Kategorien sind:

  1. Unit-Tests – Hier wird überprüft ob ein kleiner Bereich einer Software, meist eine Funktion oder Methode, für eine bestimmte Eingabe die erwartete Ausgabe liefert. Hat man z.B. in seiner Software eine Funktion zum addieren von zweier Ganzzahlen geschrieben so könnte man hier überprüfen, dass diese, wenn man ihr die Zahlen 1 und 2 übergibt, 3 zurückgibt. Das klingt zunächst erst einmal sehr trivial, hier wäre es beispielsweise auch sinnvoll zu überprüfen, dass nach Eingabe von “eins” + 2 ein Fehler zurück gegeben wird und nicht so etwas wie “eins2”, oder wie mit sehr grossen oder negativen Ganzzahlen, Fließkommazahlen und komplett anderen Werten umgegangen wird.
  2. Fuzzy Testing – Hier gibt es keine eindeutigen Eingabe- und Ausgabewerte sondern es werden Zufallswerte eingesetzt, um durch das häufige Ausführen von Tests zufällig werteabhängige Fehler zu finden. Ein Beispiel wäre analog zum Unittest von obiger Funktion ein Test, der überprüft das nach Eingabe von zwei positiven Ganzzahlen (nicht inklusive der 0) immer eine Zahl zurückgegeben wird die größer ist als beide Eingebewerte.
  3. Integration-Tests – Diese sollen das korrekte Zusammenspiel mehrerer Funktionen oder anderer verknüpfter Softwarekomponenten überprüfen. Hier könnte man z.B. überprüfen, dass sich ein Datensatz in einer Datenbank nach Aufruf einer Schnittstelle (API) in korrekter Weise ändert.
  4. Acceptance- oder End-to-End-Tests – Hier wird die Ein- und Ausgabe durch alle Schichten der Software von der Benutzereingabe bis zur Ausgabe an den Benutzer getestet.
     Bei webbasierter Software kommen hier häufig Technologien zum Einsatz, die einen Browser “fernsteuern” und dann “auslesen”. Ein Beispiel wäre ein Test in dem ein Benutzer versucht eine geschützte Seite aufzurufen. Dann wird überprüft ob er zur Login Seite umgeleitet wird. Nach Eingabe korrekter Anmeldungsinformationen wird überprüft ob er nun weiter zur ursprünglich aufgerufen Seite weitergeleitet wird, sodass ein erneuter Aufruf ohne den Umweg über den Login möglich ist. Danach meldet man den Benutzer wieder, über einen Klick auf einen ensprechenden “Abmelden” Button, ab und überprüft anschliessend das die ursprüngliche Umleitung wieder erfolgt.
  5. Regression-Tests – Wird eine bestehende Anwendung weiterentwickelt oder bekommt zum Beispiel ein Content Management System einer Webseite ein Update so möchte man in der Regel sicherstellen, dass die bisherige Anwendung oder Webseite noch genauso funktioniert oder aussieht wie bisher. Mit einem Regression Test kann man dies prüfen und quasi einen vorher-nachher-Vergleich anstellen.

Weitere Software Testverfahren

     Des weiteren gibt es auch noch Testverfahren die in der Regel nicht automatisiert oder nur halb-automatisiert durchgeführt
      werden. Die Bekanntesten sind wahrscheinlich:

  1. Last-Tests – Diese dienen der Überprüfung der Infrastrukur. Hier wird z.B. getesten wie schnell ein System reagiert, wenn innerhalb von einer Minute 10000 verschiedene Nutzer auf dieses versuchen zu zugreifen.
  2. Penetration-Tests – Diese dienen der Überprüfung der Sicherheit einer Software. Hier wird in der Regel durch Fachpersonal versucht Informationen über das System zu sammeln und dann zu überprüfen ob bekannte Sicherheitslücken nicht behoben wurden.
  3. Vulnerability Scans – Nachdem Sicherheitslücken entdeckt und bekannt werden, wird im Besten Fall ein entsprechender Eintrag in einer Datenbank mit bekannten Schwachstellen aufgenommen. Dadurch entsteht nach und nach ein Katalog aller bereits bekannten Sicherheitslücken. Mit darauf spezialisierter Software kann eine Anwendung auf Basis dieses Katalogs gegen alle bekannten Sicherheitslücken getestet werden.

Warum sind automatisierte Softwaretests wichtig?

Das testen von Software ist ein eigenens Fachgebiet und es gibt noch eine ganze Reihe weiterer Tests, die durchgeführt werden können. Nachdem wir aber nun grundsätzlich mit den verschiedenen Testtypen bekannt sind stellt sich natürlich die Frage: Was soll das ganze überhaupt? Ein direkter Nutzen der letzten Testtypen erschließt sich intuitiv. Insbesondere wenn ein Produkt öffentlich zugägnglich ist sollte überprüft werden, dass dieses gegenüber bekannten Angriffen immun ist und seine Infrastruktur auch dem maximal zu erwartendem Nutzeransturm standhalten kann.

Aber warum sollte man Tests automatisieren und immer wieder ausführen? Grade bei Tests mit festen Eingabewerten sollte die Ausgabe doch in jedem Fall immer gleich bleiben, oder?

Das stimmt nur wenn sich der Quellcode, des zu testenden Bereichs der Software inkl. der von ihm genutzten Bibliotheken, nicht ändert. Gerade wärend der aktiven Entwicklung einer Software unterliegen die einzelnen Bereiche aber einem ständigem Wandel. Der Großteil der beim Programmieren, in einem vorhandenen Modul gemachten Änderungen, bestehen aus veränderten und nicht aus neu hinzugefügten oder gelöschten Codezeilen. Dies gilt insbesondere beim sogenannten Refactoring, also der Umgestaltung eines ganzen Code Bereichs. Das Refactoring kann sinnvoll sein um technische Schuld abzubauen und so die langfristige Wart- und Erweiterbarkeit sicherzustellen.

Technische Schuld

Als technische Schuld bezeichnet man dabei häufig zuvor genommene Abkürzungen, zum Beispiel aus Zeitdruck bei der Entwicklung oder nicht ganz sauber gelöste Implementierungen. Das führt in der Regel zu einem immer schwerer wartbaren Code und damit ansteigenden Entwicklungskosten bei zunehmender Entwicklungszeit bei der Weiterentwicklung.

Vorteile von automatisierten Tests und Softwareentwicklung

Selbst wenn ein Projekt eine gewisse Reife und Vollständigkeit erreicht hat, wird es mit der Zeit nötig, die Versionen der eingesetzen Bibliotheken zu erhöhen. Dies kann z.B. notwendig sein weil eine Sicherheitslücke in einer eingesetzten Bibliothek gefunden wurde.  

Nun kommt die Verknüfpung zwischen automatisierten Tests und Softwareentwicklung zum Tragen.
Die Tests können beim Entwickler, nach dem Durchführen einer Änderung, einfach durchlaufen werden und stellen so sicher, dass alle überprüften Komponenten noch die in den Tests hinterlegten Bedingungen erfüllen. Dies lässt sich sogar als Entwicklungsmethodik – dem sogenanntem Test Driven Development (TDD) nutzen: Hier schreibt man zu jeder neu hinzuzufügenden Komponente zunächst eine möglichst präzise Verhaltensbeschreibung in Form einzelner Tests. Nach jeder Code Änderung wird der Test zum gerade implementierten Verhalten ausgeführt. Die Komponente funktioniert wie geplant wenn alle Tests fehlerfrei durchlaufen werden.

Unabhängig davon ob man nach TDD entwickelt oder nicht, sollten alle vorhandenen Tests auch immer von einem sogenannten Continous Intergration (CI) Server durchlaufen werden, sobald ein Entwickler diesen in der Quellcodeverwaltung hinterlegt. So lässt sich in Verbindung mit hochqualitativen Integration- und Acceptancetests überprüfen, dass auch die Kombination der Änderungen verschiedener Entwickler noch alle hinterlegten Kriterien erfüllt. Dies kann tatsächlich öfters nicht der Fall sein, wenn Systeme einen höheren Komplexitätsgrad erhalten und lässt sich durch manuelle Qualitätssicherung nur sehr zeitaufwendig überprüfen.

Die Testabdeckung

Kommen wir nun zur Messgrösse der Eingangs erwähnten Testabdeckung. Alle Bereiche des Programmes, die automatisiert durchlaufen werden, würden bei offensichlichen syntaktischen Fehlern direkt anschlagen. Insofern ist eine hohe Testabdeckung auf jeden Fall wünschenswert. Gerade der Einsatz der TDD Methodik führt zu einer erfreulich hohen Testabdeckung. Es wird von Anfang an sichergestellt, dass jede konkrete Implementierung durch einen Test überprüft wird. Hat also jedes Projekt das in TDD Entwickelt wurde “gute Tests”, und enthält keinerlei Fehler mehr?
 Leider nein. Eine 100% Testabdeckung kann zwar syntaktische, aber leider keine Inhaltlichen Fehler ausschliessen. Tests überprüfen eben nur genau das, was man im vorhinein definiert hat. Ein inhaltlicher Fehler, der in einer Situation auftritt an die niemand gedacht hat, wird somit höchstens zufällig bei einem Test einen Fehler auslösen.

Verschidene Testtypen für eine höhere Qualität der Software

Die Wahrscheinlichkeit für solche “Zufallsfunde” kann durch ausgiebige Acceptance-Tests erhöht werden. Breite Integrations-Tests dagegen helfen, Seiteneffekte von Änderungen in anderen Softwarekomponenten zu finden. Unit-Tests sind essentiell, um die korrekte Arbeitsweise einer Komponente nach einer Änderung sicherzustellen.

Es ist also wichtig, Software mit einer Mischung der verschiedene Testtypen zu durchlaufen, um eine höhere Qualität garantieren zu können. Die Testabdeckung sagt aber nichts über die Qualität und Art der einzelnen Tests aus. So kann man 100% Testabdeckung erreichen, indem man für jede Komponente Unittests schreibt die zwar die Komponenten durchlaufen, aber ihre Interaktion komplett ignorieren.

Unser Fazit

Zusammenfassend lässt sich sagen, dass die Qualität der automatisierten Tests einer Software entscheidend für die langfristige Lauffägigkeit und Wartbarkeit eines Projektes ist. Zudem, dass sich diese nicht in einer einzelnen Prozentzahl ausdrücken lassen, sonderen eine differenzierte, detailierte Betrachtung der vorhandenen Tests erforderlich ist.