Session D-TEST

 Testmethoden und Vorgehensweisen 

Manfred Rätzmann (
raezz@t-online.de)


Tests, ganz allgemein gesehen

Tests sind die wohl am häufigsten durchgeführten Maßnahmen zur Software-Qualitätssicherung. In einigen Fällen sogar die einzigen. Das ist bedauerlich, da zum Beispiel bei rechtzeitigen Reviews in der Produktentwicklung mehr und andere Fehler gefunden werden als bei Tests und zudem Reviews meistens auch kostengünstiger sind als Tests. Nun kann sicherlich niemand auf Tests verzichten, nur weil regelmäßige Reviews stattgefunden haben. Die Anzahl der notwendigen Wiederholungen von Tests nach einer Fehlerkorrektur kann aber drastisch reduziert werden, wenn es nicht mehr so sehr viele Fehler zu korrigieren gibt.

In dieser Session sollen unterschiedliche Testmethoden betrachtet werden, die für unterschiedliche Zwecke entwickelt wurden. Zunächst aber ein paar grundlegende Gedanken zum Thema "Testen von Software".

Ein Test läuft eigentlich immer in 3 Stufen ab.

  1. Planung
  2. Durchführung
  3. Auswertung

Auch wenn die testende Person diese 3 Stufen vielleicht nicht bewußt wahrnimmt, sind sie doch vorhanden - oder anders: Wenn eine der 3 Stufen fehlt, kann es sich schwerlich um einen Test handeln.

Wenn sich jemand an einen Computer setzt, ein Programm aufruft und ausprobiert, wie dies und jenes funktioniert, führt er oder sie keinen Test durch. Erst wenn er oder sie an einer bestimmten Stelle stutzig wird und den vermuteten Fehler im Programm nachzuvollziehen versucht, kommt eine Testsituation zustande. Diese enthält dann typischerweise die oben angegebenen Phasen Planung ("Wenn ich dieses Feld freilasse und trotzdem auf OK klicke, wird das Programm wahrscheinlich abstürzen..."), Durchführung ("schau'n mer mal...") und Auswertung ("Genau: Absturz!").

Planung:

  • Tests sind dazu da, Fehler zu finden. Ein erfolgreicher Test ist also ein Test, bei dem ein Fehler gefunden wurde. Das muß bei der Planung eines Tests berücksichtigt werden. Und auch bei der Auswahl der Personen, die Tests durchführen. Entwickler eines Programms gehen vielleicht nicht mit der selben Energie daran, das eigene Programm zu überlisten, wie fremde Personen. Der Entwickler will, daß das Programm rund läuft - der Tester hat sein Erfolgserlebnis, wenn es abstürzt! Das sind äußerst konträre Interessenslagen.
  • Mit Tests kann nur das Vorhandensein von Fehlern nachgewiesen werden, nicht die Fehlerfreiheit eines Programms. Wenn man schon 100 Fehler gefunden hat, heißt das nicht, daß nicht noch 1000 Fehler unentdeckt sind. Wenn Sie keinen Fehler mehr finden, kann das zum einen heißen, daß Ihre Software fehlerfrei ist, zum anderen aber auch, daß Ihre Testmethoden und Testfälle nicht ausreichen um die tatsächlich noch vorhandenen Fehler zu finden.
  • Wenn Sie das zu testende Programm von innen kennen ("White-Box Tests") werden Sie andere Tests planen als wenn Sie das zu testende Programm nur von der Oberfläche her kennen ("Black-Box Tests"). Dabei ist nicht das eine Verfahren besser als das andere. Sie haben neben anderen Voraussetzungen auch andere Zielsetzungen. Dazu weiter unten mehr.

Durchführung

  • Tests alleine verbessern die Softwarequalität noch nicht. Das wäre so, als könnte man alleine dadurch abnehmen, daß man sich täglich auf die Waage stellt. Die Ergebnisse der Tests führen normalerweise zur Überarbeitung des Programms und zur Fehlerkorrektur. Im Anschluß daran muß exakt der selbe Test noch einmal durchlaufen werden, damit man sicher sein kann, daß 1. der Fehler richtig korrigiert wurde und 2. der richtige Fehler korrigiert wurde.
  • Das Testergebnis muß in geeigneter Form festgehalten werden. Gut gesagt - nur: welche Form ist geeignet, welche nicht? Zum einen muß das Ergebnis schriftlich festgehalten werden, ein Anruf beim Entwickler oder ein Gespräch in der Mittagspause reicht also nicht. Das Testdokument sollte neben allgemeinen Angaben (Namen des Testers, Datum des Tests, getestetes Programm, Programmversion) die 3 Stufen des Tests dokumentieren, nämlich Planung (was soll getestet werden), Durchführung (wie wurde getestet) und Auswertung (was wäre das korrekte Ergebnis, was war das tatsächliche Ergebnis).
  • Die Umgebungsbedingungen unter denen der Test ablief, gehören zur Dokumentation der Durchführung. Wichtig ist, daß bei einem Wiederholungstest nach der Fehlerkorrektur die gleichen Bedingungen wieder hergestellt werden.

Auswertung

  • Einen Testlauf auszuwerten bedeutet immer, das eigentlich korrekte Ergebnis mit dem tatsächlichen Ergebnis zu vergleichen. Das eigentlich korrekte Ergebnis sollte zu diesem Zweck eindeutig zu ermitteln sein. Man kann nicht testen, ob ein Programmergebnis korrekt ist, wenn nirgends festgelegt ist, wie das korrekte Ergebnis aussieht.
  • Zur Auswertung von Tests brauchen Sie also dokumentierte Vorgaben. Was weder in der Aufgabenbeschreibung, noch in den UseCases oder den Layout-Vorgaben zur Oberflächengestaltung, in ausgedruckten Beispielreports oder ähnlichem dokumentiert ist, kann auch nicht getestet werden.

Was tun mit den Ergebnissen? Normalerweise werden sie an die Entwickler weiter gegeben mit der Aufforderung, die gefundenen Bugs zu beheben. Wenn dieser Weg aus irgend welchen Gründen nicht möglich ist, haben Sie mit den Testergebnissen trotzdem noch eine Liste in Händen, die aussagt, wie zuverlässig die Software in bestimmten Situationen arbeitet. Eine solche Liste kann zum Beispiel als "bekannte Fehler" Eingang in das Handbuch finden. Zusätzlich lohnt es sich vielleicht, die am häufigsten gemachten Fehler bei der Entwicklung festzustellen. Anhand dieser Angaben können Sie Entscheidungen zu notwendigen Aus- und Weiterbildungmaßnahmen treffen. Oder Ihren Softwareentwicklungsprozeß ändern, andere Tools einsetzen, etc.

Black & White Box Tests

Testverfahren können grundsätzlich in Black-Box und White-Box Verfahren unterschieden werden.

Black-Box Tests

Beim Black-Box Verfahren ist vom Testobjekt - einem Programm oder einem Programmteil - nur die definierte Schnittstelle zur Außenwelt bekannt. Dies kann die Anwendungsoberfläche, eine interne Modulschnittstelle oder die INPUT/OUTPUT Beschreibung eines Batch-Prozesses sein. Black-Box Tests prüfen, ob die Schnittstellendefinition in allen Situationen eingehalten wird. Black-Box Tests werden in der Regel vom Auftraggeber im Rahmen der Produktabnahme durchgeführt. Sie überprüfen, ob das Produkt allen festgelegten Anforderungen entspricht. Die Testfälle werden also an Hand der Aufgabenbeschreibung erstellt.

Der Entwickler tut natürlich gut daran, vor der Freigabe des Produktes diese Tests ebenfalls durchzuführen. So hat er oder sie die Chance, Fehler zu finden, bevor der Auftraggeber sie findet.

White-Box Tests

Beim White-Box Verfahren ist das Innenleben des Testobjektes bekannt. Die Testfälle werden auf Grund dieser Kenntnis erstellt. White-Box Tests sind also spezifische Entwicklertests und stellen sicher, daß jede implementierte Funktion zumindest einmal durchlaufen und auf korrektes Verhalten überprüft wurde. Die Überprüfung der Ergebnisse eines White-Box Tests kann ebenfalls mit der Kenntnis der Systemgegebenheiten arbeiten. So muß eine Buchung nicht durch Ausdruck des Journals und Überprüfen der angezeigten Kontensalden an der Programmoberfläche geprüft werden, sondern kann, da die Datenstruktur der Konten bekannt ist, direkt an Hand der entstandenen Daten verifiziert werden. Davon unberührt bleibt die Tatsache, daß der korrekte Journaldruck und die richtige Anzeige der Kontensalden ebenfalls geprüft werden müssen. White-Box Tests ersetzen also keine Black-Box Abnahmetests - sie machen sie nur weniger spannend.

Im folgenden werden wir uns hauptsächlich mit White-Box Tests beschäftigen. Mit den Testverfahren also, die die Entwickler eines Produktes vor der Übergabe des Produkts an den (internen) Auftraggeber anwenden können.

Welche Tests wann?

Unit Testing

Als Unit Testing bezeichnet man eine Zusammenfassung aller Tests, die eine Programmeinheit betreffen. Fehler, die hier gefunden werden, werden schnellstmöglich ausgemerzt. Der Test, der den Fehler aufgedeckt hat, wird nach Behebung des Fehlers wiederholt. Unit Testing beschäftigt sich mit folgenden Fragen:

  • Sind alle Anforderungen, die diese Programmeinheit betreffen, korrekt umgesetzt?
  • Hier geht es nicht darum, ob die Anforderungen selbst korrekt sind, sondern ob die niedergelegten Anforderungen vom Programm erfüllt werden. Ausgangspunkt für die durchzuführenden Tests ist also der Anforderungskatalog, der hoffentlich jede Einzelanforderung eindeutig identifiziert.
  • Sind alle Designentscheidungen korrekt umgesetzt?
  • Die korrekte Umsetzung des Systemdesign in einer Programmeinheit wird im allgemeinen durch Reviews überprüft. Designentscheidungen, die mit Tests abgeprüft werden können, betreffen zum Beispiel die Layoutvorgaben für die Anwendungsoberfläche.
  • Arbeiten alle internen Funktionen fehlerfrei?
  • Das sind die Basistests. Die internen Funktionen, Berechnungen usw. werden daraufhin überprüft, ob sie die richtigen Ergebnisse zurückliefern. Wichtige Tests dabei sind, ob die Funktion übergebene Parameter abprüft, Parameterfehler abfängt, mit Grenzwerten richtig umgeht und so weiter. Eine Minimalanforderung für diese Basistests ist, daß jede Sourcecodezeile zumindest einmal durchlaufen wurde.

Aufwandsabschätzung

Wie hoch sollte der Aufwand für Unit Testing im Verhältnis zum Aufwand für das gesamte Projekt angesetzt werden? Steve Mc Connel spricht in seinem Buch: Code Complete (sehr empfehlenswert!) von 8 bis 35 %, je nach Gesamtkomplexität. Bei steigender Komplexität nimmt der Anteil des Unit Testing ab.

Integration Testing

Im Integration Testing wird überprüft, ob das Zusammenspiel der Programmeinheiten korrekt funktioniert. Programmeinheiten, die vorher im Unit Testing überprüft wurden, werden zu größeren Einheiten zusammengebaut. Integration Testing beschäftigt sich also nicht mehr mit den Implementierungsdetails der einzelnen Einheiten sondern mit den Anforderungen und Designentscheidungen, die für das Zusammenspiel gelten. Das können zum Beispiel Anforderungen sein, die Antwortzeiten oder den Workflow zwischen einzelnen Modulen, Zugriffsschutz bei Mehrbenutzerbetrieb oder Zugriffsrechte einzelner Benutzer betreffen, usw.

Aufwandsabschätzung

Der Aufwand für Integrationstests nimmt mit steigender Komplexität des Gesamtsystems im Verhältnis zum Gesamtaufwand zu. Er wird meist zwischen 5 % für einfache Systeme und 30 % für komplexe Systeme angegeben.

System Test

Wenn das Gesamtsystem oder eine Ausbaustufe erstellt ist, erfolgt ein Systemtest. Der Systemtest betrifft Fragen wie Vollständigkeit, Performance bei hoher Belastung, Sicherheit, Integration in die Systemumgebung und so weiter. Die Vorgehensweise bei Systemtests wird in dieser Session nicht behandelt.

Aufwandsabschätzung

Der Aufwand für Systemtests wächst wie bei den Integrationstests mit der Komplexität des Systems. Er wird mit einer Bandbreite von 5 - 25 % angegeben.

Regression Testing

Wenn an einem Teil des Programms Änderungen oder Erweiterungen vorgenommen wurden, muss der Programmteil einem erneuten Unit Test unterzogen werden. Außerdem müssen alle mit diesem Programmteil zusammenarbeitende Teile einem erneuten Integrationstest unterzogen werden. Damit soll sichergestellt werden, daß die Änderungen oder Erweiterungen keine neuen Fehler in das Produkt gebracht haben.

Aufwandsabschätzung

Der Aufwand für Regressionstests hängt eng mit den verwendeten Testverfahren zusammen. Insbesondere bei ausschließlich manuellen Testverfahren kann er leicht 100% des ursprünglichen Testaufwands erreichen. Bei automatisierten Testverfahren liegt er im Schnitt bei 30% des ursprünglichen Testaufwands der beteiligten Programmteile.

Tests erstellen

Manuelle Testverfahren

Manuelle Testverfahren sind sehr zeit- und damit kostenintensiv. Leider lassen sie sich meistens nicht ganz vermeiden. Überall da, wo zum Beispiel Ausdrucke auf Korrektheit geprüft werden müssen, kommt man kaum umhin, das produzierte Blatt Papier in die Hand zu nehmen und zu überprüfen. Ein rein manueller Test wird erstellt indem die Testdokumentation erstellt wird. Sie beschreiben also, was Sie testen wollen, halten Ihre Vorgehensweise dabei während der Durchführung fest und notieren das Ergebnis. Häufig ist es aber möglich, zumindest einen Teil der Testdurchführung zu automatisieren. Dann muß nur noch die Auswertung manuell erfolgen.

Automatisierte Testverfahren

Tests sollten, wenn irgend möglich, automatisiert werden. Die beiden in dieser Session und in meiner Vendor-Session zu FoxRunner vorgestellten Verfahren für automatisierte Tests sind "Testcode" und "Scripting".

Testcode ist ein Verfahren zum Test von Klassenmethoden. Testcode eignet sich besonders für Basistests, d.h. zum Test der internen Funktionen. Mit Testcode können Sie sicherstellen, daß jede Sourcecodezeile einmal durchlaufen wurde.

Scripting bedeutet, VFP-Programme scriptgesteuert ablaufen zu lassen. Scripting eignet sich also immer dann, wenn das Zusammenspiel mehrerer Objekte getestet werden soll oder die Anwendungsoberfläche in den Testablauf einbezogen wird. Scripting ist also das Verfahren, das bei allen Integrationstests angewandt wird.

Auch das Erstellen eines automatisierten Tests beginnt mit der Planung und der Dokumentation des geplanten Tests. Zur Durchführung muß dann aber entweder der Testcode oder das Script erstellt werden, das den geplanten Test ausführt und das Ergebnis des Programmlaufs mit dem korrekten Ergebnis vergleicht. Da Testcode und Script ebenfalls von Programmierern in einer Programmiersprache verfasst werden, können sich auch hier Fehler einschleichen. Bis zum fehlerfreien Durchlauf eines Tests entsteht also hier ein teilweise nicht unerheblicher Aufwand. Dieser Aufwand entsteht allerdings nur einmal. Wenn ein Wiederholungstest oder ein Regressionstest notwendig wird, zahlt sich der einmal geleistete Aufwand sofort wieder aus.

Bestimmte Gegebenheiten können rein technisch nur durch die Erstellung eines Testprogramms, d.h. automatisiert, getestetet werden. So finden sich in fast jedem VFP-Programm Codezeilen wie: 

 
      oIrgendwas = createobject(„IrgendeineKlasse“)
      if type(„oIrgendwas.Name“) = „C“
         * Objekt ist instantiiert
      else
         * Instantiierung ist fehlgeschlagen
      endif

Dabei wird der Else-Zweig bei manuellen Tests wahrscheinlich niemals durchlaufen, da das Instantiieren des Objektes immer funktioniert. Ein manuelles Testverfahren müßte dafür sorgen, daß bis zu dieser Stelle im Programm zunächst alles glatt geht und dann auf irgendeine Weise das Instantiieren dieses Objektes fehlschlägt. Da sowas von außen reichlich kompliziert oder eventuell gar nicht zu bewerkstelligen wäre, verzichten die meisten Entwickler, die ausschließlich manuelle Testverfahren benutzen, darauf, alle ihre IFs und ELSEs zu Ausnahmebedingungen zu testen. Die entsprechenden Codezeilen werden dann das erste Mal durchlaufen, wenn sich beim Kunden aus irgendeinem Grund eine solche Ausnahmebedingung einstellt. Was dabei passiert, erfährt als erster Ihre Service-Hotline.

Design for Tests

Der Ehrgeiz der Produktentwickler sollte unbedingt dahin gehen, manuelle Testverfahren zu vermeiden. Das Systemdesign ist der Prozeßschritt, bei dem die Notwendigkeit oder Vermeidbarkeit von manuellen Tests entscheidend beeinflußt wird. So kann zum Beispiel die Designentscheidung, Reports immer aus druckaufbereiteten temporären Tabellen auszugeben, bedeuten, daß manuelle Testauswertungen sich auf die Überprüfung des Drucklayouts beschränken können. Die Korrektheit der Ausgabeergebnisse kann dann automatisch durch Analyse der temporären Tabellen geschehen.

Eine weitere für Tests nützliche Designentscheidung ist es, Oberfläche und Verarbeitung strikt zu trennen. So können die Verarbeitungsfunktionen komplett durch Testcode getestet werden. Die Scripts für den Oberflächentest können sich dann auf die korrekte Anzeige, den Workflow etc. beschränken.

Testfälle finden

Basistests

Die mit dem Testcode-Verfahren durchgeführten Basistests überprüfen zum einen die Korrektheit der internen Funktionen und führen gleichzeitig eine Sourcecode Validierung durch. Sourcecode Validierung bedeutet, daß jede Codezeile zumindest einmal durchlaufen wird.

Aus diesen beiden Aspekten ergeben sich die Testfälle, die für eine interne Funktion (eine Klassenmethode) benötigt werden. Zum Auffinden der Testfälle wird die Sourcecodestruktur herangezogen. Aufgabe beim Erstellen der Testfälle ist es, alle möglichen Pfade durch die Funktion zu ermitteln. Die Testfälle müssen als Minimum sicherstellen, daß alle Pfade durchlaufen werden.

  1. Best-Case
  2. Der erste Testfall ist dabei der Best-Case Fall, das heißt, alle in der Funktion vorliegenden Abprüfungen verlaufen positiv, die Funktion wird auf ihrem Idealpfad durchlaufen.
  3. IF, FOR ... ENDFOR, DO WHILE ... ENDDO Jede Bedingung in der Funktion generiert mindestens einen weiteren Testfall. Dieser Testfall entspricht der Situation, daß die Bedingung vom Idealpfad abweicht. Wenn die Bedingung ein zusammengesetzter Ausdruck ist, wird für jeden Teil des Ausdrucks ein eigener Testfall generiert. 
    IF type("cAlias") <> "C" or empty(cAlias) ergibt also zwei Testfälle. Im ersten ist cAlias nicht vom Typ Character, im zweiten ist cAlias leer. Das gleiche gilt für die Bedingungsausdrücke in FOR oder DO WHILE Schleifen.
  4. DO CASE ... OTHERWISE ... ENDCASE Für jeden CASE-Fall, außer dem CASE des Idealpfads, wird ein eigener Testfall fällig. Ebenso für OTHERWISE, gleichgültig, ob der OTHERWISE-Fall im Sourcecode explizit aufgeführt wird, oder nicht. Wenn eine CASE-Bedingung zusammengesetzt ist, muß auch hier für jeden Teil der zusammengesetzten Bedingung ein gesonderter Testfall entwickelt werden.

Mit den oben angegebenen Testfällen haben Sie sichergestellt, daß jede Sourcecodezeile einmal durchlaufen wird. Leider sind das aber noch nicht alle Fälle, die überprüft werden müssen. Weitere Testfälle können zur Überprüfung des Algorithmus notwendig werden. Wenn die Funktion Parameter entgegennimmt, kann eine inhaltliche Prüfung der Rückgabewerte bei unterschiedlichen Parametersetzungen notwendig werden, und so weiter.

Integrationstests

Bei Integrationstests werden die Schnittstellen der zusammenarbeitenden Programmteile überprüft. Der erste Test stellt, wie bei den Basistests, sicher, daß bei einer korrekten Verwendung der Schnittstelle auch ein korrektes Ergebnis erzielt wird. Weitere Tests prüfen die für die Schnittstelle festgelegten Defaultsetzungen ab. Dabei werden also nacheinander alle Schnittstellenparameter weg gelassen, für die Default-Werte definiert sind. In einer dritten Stufe wird überprüft, ob eine falsche Versorgung der Schnittstelle richtig abgefangen wird.

Oberflächentests

Diese überprüfen die korrekte Anzeige der Anwendungsergebnisse, den Workflow, das Abweisen von falschen Eingaben usw. Zur Erstellung der Testfälle kann man auch hierbei vom Idealfall ausgehen, der zunächst mal problemlos abgearbeitet werden muß. Danach werden mögliche Sonderfälle getestet und schließlich Fehlersituationen wie die Eingabe fehlerhafter Daten durch den Anwender. Der Idealfall und die Sonderfälle können an Hand der Anforderungsbeschreibung ermittelt werden. Bei den Fehlersituationen ist dies nicht so einfach, da meistens nicht alle möglichen Fehler und deren korrekte Reaktion darauf in der Anforderungsbeschreibung festgehalten sind. Da ist die Erfahrung des Testentwicklers gefragt.

Speziell für den Tests des Workflows sind die in der Analysephase erarbeiteten UseCases sehr hilfreich, da diese die Benutzung des Systems aus Anwendersicht wiedergeben.

Testdaten bereitstellen

Alle Tests müssen reproduzierbar sein. Das bedeutet, daß Sie bei einer Wiederholung des Tests die Testdaten (und sonstigen Rahmenbedingungen) so wieder herstellen müssen, wie sie zu Beginn des ersten Tests vorlagen. Da Tests typischerweise genau auf die jeweilige Testsituation zugeschnittene Bedingungen brauchen, empfiehlt es sich, für die Tests eine von der Entwicklungsumgebung abgekoppelte Testumgebung zu schaffen. Zu dieser Testumgebung gehören natürlich auch die Testdaten. Da diese eventuell durch die Tests selbst verändert werden, sollten Sie einen Ausgangsstand Ihrer Testdaten separat pflegen und sichern.

Testergebnisse festhalten

Testergebnisse sollen schriftlich festgehalten werden. Das gilt vor allem für Tests, die nicht vom Entwickler selbst im Rahmen des Unit Testing durchgeführt werden. Unit Tests mit dem Testcode Verfahren werden im Testcode selbst dokumentiert. Das von mir in der Session V-Fox vorgestellte Tool zur Testautomatisierung legt die Ergebnisse der durchgeführten Tests in einer Testprotokolldatei ab, die nach dem Test automatisch angezeigt wird. Das reicht bei Testcode Durchläufen meistens aus. Wenn der Durchlauf dokumentiert werden soll, kann die Testprotokolldatei ausgedruckt oder weggesichert werden.

Für manuelle Tests oder Tests auf der Basis von VFP Scripts habe ich ein Testprotokoll als Muster entwickelt, das Sie auf der DevCon CD finden. Das Testprotokoll teilt sich in Testbeschreibung und Bearbeitungsvermerke. Damit eignet es sich auch als Durchlaufprotokoll für die weitere Bearbeitung des gefundenen Fehlers nach dem Test.