252

Lernziele zum Kapitel 8 Nach diesem Kapitel sollten Sie 1. verschiedene Verfahren der analytischen Qualitätssicherung kennen und einordnen können, 2. zu einem gegebenen Programm oder Programmteil einen kompakten Kontrollflußgraphen erstellen und kontrollflußorientierte Testverfahren anwenden können, 3. aus einer gegebenen Spezifikation Ein- und Ausgabeäquivalenzklassen ableiten und funktionale Testverfahren anwenden können, 4. Inspektion und Walk-Through erklären und voneinander abgrenzen können, 5. die Zusammenhänge zwischen verschiedenen Testaktivitäten und Testdokumenten erläutern können.

253

8. Analytische Qualitätssicherung 8.1 Motivation und grundlegende Definitionen Mit dem vorigen Kapitel sind unsere Ausführungen über grundlegende (imperative) Programmierkonzepte beendet. Während es bisher darum ging, wie man einfache, wohlstrukturierte Programme erstellt, beschäftigen wir uns im letzten Teil des Kurses mit der analytischen Qualitätssicherung, die Verfahren zur Sicherstellung der Korrektheit bzw. Fehlerfreiheit von Programmen zum Gegenstand hat. Fehler werden zu jedem Zeitpunkt der Programmentwicklung gemacht, und es wäre naiv zu glauben, daß eine höhere Sprache oder eine bessere Methodik die Korrektheit von Programmen so gut wie sicherstellt. Die Fehlerquelle ist der Mensch selbst, und solange Menschen Programme entwickeln, werden diese fehlerhaft sein. Allerdings werden die Anzahl und Qualität der Fehler sehr wohl auch durch die verwendete Entwicklungsmethodik und -umgebung bestimmt. Hieraus resultiert unsere erste Faustregel:

erste Faustregel

Jedes Programm ist fehlerhaft, es sei denn, wir haben uns vom Gegenteil überzeugt. Welche Folgerungen müssen wir daraus ziehen? Zunächst vor allem eine: Die Funktionsfähigkeit von praxistauglichen Programmen darf sich nicht auf Vermutungen stützen, da ein Fehlverhalten oft schwerwiegende Folgen haben kann. Wie können wir nun die Korrektheit bzw. Fehlerfreiheit eines Programms sicherstellen? Dazu erinnern wir uns an die Problemspezifikation in Abschnitt 2.1 und formulieren etwas präziser: ein korrektes Programm erzeugt für jede Eingabe, welche die Vorbedingung erfüllt, eine Ausgabe, welche die Nachbedingung erfüllt. Wird die Korrektheit eines Programms formal bewiesen, sprechen wir von der Verifikation eines Programms. Formale Verifikationsverfahren sind mathematisch kompliziert und sehr aufwendig durchzuführen. Desweiteren sind sie nur anwendbar, wenn eine formale Problemspezifikation vorliegt. Diese Einschränkungen machen Verifikationsmethoden nur in bestimmten Anwendungsbereichen sinnvoll einsetzbar. Für die meisten administrativen und kaufmännischen Programme stehen formale Verifikationen in keinem vernünftigen Kosten-Nutzen-Verhältnis. Ganz anders sieht es aber in Bereichen aus, in denen inkorrekte Programme extrem teure Schäden hervorrufen oder sogar Menschenleben fordern können. Zu diesen sicherheitskritischen Programmen gehören z.B. Steuerungs- und Überwachungsprogramme von (Kern-) Kraftwerken, Verkehrsleitsysteme, Flugüberwachungssysteme (denken Sie an den Frankfurter Flughafen), Programme, die Flugzeuge steuern und überwachen (würden Sie noch in den Airbus steigen, wenn dessen besonders kritischen Softwarekomponenten nicht verifiziert wären?) oder Programme für NASA-Weltraumprojekte. Ist ein vollständiger formaler Nachweis der Korrektheit nicht durchführbar, müssen wir uns nach einfacher durchzuführenden (aber auch schwächeren) Methoden um-

Verifikation

254

Testen

Kurseinheit IV

sehen. Eine solche Methode ist das Testen. Unter Testen verstehen wir jede Tätigkeit, die ein Programm mit dem Ziel untersucht, (möglichst viele) Fehler aufzufinden. Das Testen umfaßt nur die Fehlersuche und nicht die Fehlerkorrektur. Alle noch so ausgeklügelten Testverfahren können jedoch nur die Anwesenheit von Fehlern, aber niemals deren Abwesenheit feststellen. Ein Programm, das nach dem Testen keine Fehler mehr zeigt, muß noch lange nicht korrekt sein. Wir müssen daher daran interessiert sein, so viele Fehler wie möglich zu finden, um die Wahrscheinlichkeit für seine Korrektheit zu erhöhen. Daher sollte der Programmtest nicht vom Autor des Programms selbst durchgeführt werden, da die Gefahr einer Betriebsblindheit zu groß ist. Besser ist es, das Testen von geschulten Testern durchführen zu lassen, die außer ihrer Objektivität auch noch über entsprechendes Spezialwissen verfügen.

zweite Faustregel

Damit erhalten wir eine zweite Faustregel: Der Programmtest sollte möglichst nicht vom Programmautor durchgeführt werden. Wichtig ist, zwischen einem Fehler und einer Fehlerursache zu unterscheiden. Definition 8.1.1

Fehler Fehlerursache

Ein Fehler ist ein nach außen sichtbares Fehlverhalten eines Programms. Eine Fehlerursache ist ein inkorrekter Programmteil, der zu einem Fehler des Programms führt. Verschiedene Fehler können dabei Ausdruck derselben Fehlerursache sein. ❑ Ein Fehler liegt z.B. vor, wenn die Ausgabe eines Programms nicht der Nachbedingung der Spezifikation genügt, das Programm in eine Endlosschleife gerät oder „abstürzt“. Eine Fehlerursache kann z.B. eine falsche Anweisung, eine vergessene Initialisierung oder eine falsche Schleifenbedingung sein. Die Begriffe Testen und Debuggen grenzen wir folgendermaßen ab: Definition 8.1.2

Testen Debuggen

Als Testen bezeichnen wir das gezielte Suchen nach Fehlern. Testen umfaßt nicht das Beheben von Fehlern. Debuggen (engl. to debug: entwanzen) ist das Beheben von Fehlern. Zu einem festgestellten Fehler wird die Fehlerursache aufgespürt und anschließend korrigiert. ❑ Wir unterscheiden weiterhin zwischen Testdaten und Testfällen. Definition 8.1.3

Testdatum Testfall

Ein Testdatum ist ein Tupel bestehend aus zulässigen Eingabedaten und dem erwarteten Ergebnis bzw. den erwarteten Ausgabedaten. Unter einem Testfall verstehen

255

8. Analytische Qualitätssicherung

wir eine aus der Spezifikation oder dem Programmtext abgeleitete Menge von Testdaten. ❑ Für den Rest dieses Kapitels wollen wir die Begriffe Programm und Prozedur/Funktion synonym verwenden, solange keine Mißverständnisse möglich sind. Ein zum Test vorliegendes Programm bezeichnen wir auch als Prüfling. Beispiel 8.1.4 Wir betrachten die folgenden Typdefinitionen und die bekannte Fakultätsfunktion1: type tDefBereich = 0..12; tNatZahl = 0..maxint; function Fakultaet ( inZahl : tDefBereich): tNatZahl; { berechnet die Fakultaet von inZahl >= 0 } var i : integer; temp : tNatZahl; begin temp := 1; for i := 2 to inZahl do temp := i * temp; Fakultaet := temp end; { Fakultaet } Aus der Definition der Fakultätsfunktion wählen wir in Abhängigkeit vom Parameter inZahl zwei Testfälle: Testfall 1:

{(inZahl, inZahl!) | inZahl > 0}

Testfall 2:

{(0, 1)}.

Sinnvolle Eingabedaten für den ersten Testfall sind die obere Grenze des definierten Bereichs und eine oder mehrere Zahlen aus dem Bereichsinneren. Der zweite Testfall besteht nur aus einem Testdatum, dessen Eingabedatum die untere Bereichsgrenze ist. Als Testdaten wählen wir z.B. folgende Zahlenpaare aus Eingabedatum und richtigem Ergebnis: Testdaten 1:

(1, 1), (2, 2), (10, 3628800), (12, 479001600)

Testdaten 2:

(0, 1).

1. Die obere Grenze des Definitionsbereichs berücksichtigt die Bedingung inZahl! < maxint.

Prüfling

256

Kurseinheit IV

Ein dritter möglicher Testfall mit Eingabedaten inZahl < 0 braucht bei dieser Implementation nicht weiter untersucht zu werden. Er wird vom Typsystem von PASCAL erkannt. ❑

8.2 Klassifikation der Verfahren der analytischen Qualitätssicherung analytische Qualitätssicherung

Die verschiedenen Ansätze, welche die Korrektheit bzw. Fehlerfreiheit von Programmen zum Gegenstand haben, werden unter dem Begriff analytische Qualitätssicherung zusammengefaßt. Dazu gehören jedoch nicht nur die Verfahren der Verifikation, Analyse, Testen und Debuggen, sondern auch die zugehörigen organisatorischen Maßnahmen, also die Planung und Kontrolle von Testaktivitäten. Ein wichtiger Gesichtspunkt der analytischen Qualitätssicherung ist, daß sie eine die Programmentwicklung begleitende Tätigkeit darstellt und nicht erst am Ende der Entwicklung, z.B. in Form von Testen, einsetzt. Diesen Aspekt werden wir hier nicht weiter verfolgen, sondern in den Kursen über Software Engineering vertiefen. Die Verfahren der analytischen Qualitätssicherung unterscheiden wir zunächst aufgrund ihrer unterschiedlichen Zielsetzungen. Verifizierende Verfahren beweisen die Korrektheit eines Programms, analysierende Verfahren quantifizieren bestimmte Programmeigenschaften oder stellen diese dar und testende Verfahren versuchen, Fehler aufzudecken.

verifizierende Verfahren

Verifizierende Verfahren beweisen die Korrektheit eines Programms bezüglich seiner Spezifikation. Enthält der Beweis selbst keine Fehler, so ist dies die höchste Qualitätsstufe, die überhaupt erreichbar ist. Allerdings erfordern verifizierende Verfahren eine formale Programmspezifikation, sind sehr komplex und selbst kleine Beweise sind ohne Werkzeugunterstützung kaum möglich. Für sicherheitskritische Anwendungen ist allerdings die Verifikation wichtiger Programmteile unverzichtbar.

analysierende Verfahren

Analysierende Verfahren sind keine Tests im eigentlichen Sinn. Sie erlauben weder Korrektheitsbeweise noch dienen sie direkt der Fehlersuche, sondern sind eher — wertvolle, weil leicht automatisierbare — Hilfsinstrumente. Metriken wie die zyklomatische Zahl oder die Halstead-Metriken erlauben die Quantifizierung von Eigenschaften wie strukturelle Komplexität, Programmlänge oder Kommentierungsgrad und ermöglichen so insbesondere vergleichende Beurteilungen von Dokumenten (z.B. Programmlistings). Die Überschreitung einer kritischen, aus den Erfahrungen früherer Projekte abgeleiteten Maßzahl kann dabei ein Hinweis auf eine erhöhte Fehlerwahrscheinlichkeit sein und zu einer genaueren Untersuchung Anlaß geben. Durch Tabellen und Graphen, wie z.B. Kontrollflußgraphen und cross reference-Listen, werden gezielt spezifische Details eines Dokuments herausgearbeitet und in kondensierter, einfach interpretierbarer Form dargestellt. Einige moderne Compiler bieten derartige Funktionen bereits optional an. Wir werden auf analysierende Verfahren nicht näher eingehen und verweisen den interessierten Leser auf die in [Lig90], [Dum93] angegebene Literatur und speziell [Hal77], [McC76], [Inc90].

zyklomatische Zahl Halstead-Metrik

257

8. Analytische Qualitätssicherung

Testverfahren gliedern sich in statische und dynamische Testverfahren. Bei statischen Verfahren wird das Programm nicht ausgeführt. Statische Verfahren zeichnen sich nach Liggesmeyer durch drei Merkmale aus [Lig90]:

statische Verfahren

- Das Programm wird nicht ausgeführt, - erkannt werden nicht nur fehlerhaftes Verhalten, sondern Fehlerursachen selbst und - die Korrektheit des Programms (bzgl. seiner Spezifikation) wird nicht bewiesen. Ein wesentlicher Vorteil statischer Verfahren ist, daß ihre Anwendung nicht auf ausführbare Programme beschränkt bleibt, sondern daß sie im Grunde für alle Arten von Softwaredokumenten geeignet sind. Außerdem sind statische Verfahren einfach und bei sorgfältigem Einsatz sehr effektiv. Die wichtigste statische Technik ist der Review, in dem ein Softwaredokument von einer Gruppe von Testern mit bestimmter Zielsetzung „gelesen“ wird. Einige der in Reviews entdeckten Fehler können auch durch automatische Werkzeuge wie statische Programmanalysatoren erkannt werden, welche die Programmqualität schnell und einfach verbessern. Statische Programmanalysatoren kann man als Ergänzung des Compilers betrachten, der ja nur die syntaktische Korrektheit des Programms sicherstellt. Im Gegensatz zu statischen Testverfahren setzen dynamische Verfahren zur Fehlererkennung die Ausführbarkeit des Programms voraus. Liggesmeyer grenzt dynamische Testverfahren von den übrigen Verfahren durch drei spezifische Merkmale ab [Lig90]:

Review statische Programmanalysatoren

dynamische Verfahren

- Das Programm wird mit konkreten Testdaten ausgeführt, - das Programm wird in der realen Umgebung getestet, - es handelt sich um Verfahren, welche die Korrektheit des getesteten Programms nicht beweisen können. Dynamische Verfahren sind also systematische Ausprägungen dessen, was wir intuitiv unter Testen verstehen: Das „Füttern“ eines ausführbaren Programms mit mehr oder weniger willkürlich gewählten - Eingabedaten. Anhand der Systematik zur Auswahl der Testfälle werden strukturorientierte, funktionale und diversifizierende dynamische Tests unterschieden. Bei strukturorientierten Tests können die Testfälle kontroll- oder datenflußbezogen abgeleitet werden, funktionale Tests benutzen die Programmspezifikation für die Testfalldefinition und diversifizierende Verfahren vergleichen die Resultate verschiedener Programmversionen (für dieselbe Problemstellung). Insgesamt bilden die ca. 40 verschiedenen dynamischen Verfahren die mit Abstand stärkste Gruppe unter den Testverfahren. Neben der hier vorgestellten Klassifikation dynamischer Verfahren finden sich in der Literatur noch andere Systematisierungen, deren bekannteste wir der Vollständigkeit halber erwähnen. Als Unterscheidungskriterium werden hier Strukturinfor-

strukturorientierter Test funktionaler Test diversifizierende Verfahren

258

White Box-Verfahren Black Box-Verfahren Grey Box-Verfahren

Kurseinheit IV

mationen herangezogen, die für die Definition von Testfällen notwendig sind. White Box-Verfahren basieren auf der (internen) Struktur des Programms, die aus diesem Grund bekannt sein muß. Ist die Struktur für den Test unerheblich, so spricht man von Black Box-Verfahren. Testverfahren, die dieser Klassifikation nicht eindeutig zugeordnet werden können, weil sie zum Teil White Box- und zum Teil Black Box-Charakter besitzen, heißen Grey Box-Verfahren. Eine Klassifikation der Verfahren zur analytischen Qualitätssicherung ist in Abbildung 8.1 schematisch dargestellt, wobei die wichtigsten Vertreter der einzelnen Klassen aufgeführt sind. Die Verfahren, die in diesem Kapitel vorgestellt werden,

259

8. Analytische Qualitätssicherung haben wir mit einem „✍" bzw. „ε“ markiert. Mit„✍" markierte Verfahren gehören zum Pflichtstoff, „ε“ weist auf Exkurse hin. verifizierend

Programmverifikation symbolische Programmausführung

analysierend

Metriken Graphen & Tabellen Anomalienanalyse

testend statisch

statische Programm-Analysatoren ε Reviews ✍ Audits

dynamisch

strukturorientiert kontrollflußbezogen

Anweisungsüberdeckung ✍ Zweigüberdeckung ✍ Bedingungsüberdeckung einfache minimale mehrfache ✍ mehrfache Boundary-interior Pfadtest ✍ Pfadüberdeckung

datenflußbezogen ε

Defs/Uses-Kriterien all defs-Kriterium all uses-Kriterium ε all du-paths-Kriterium Datenkontext-Überdeckung

funktional

funktionale Äquivalenzklassenbildung ✍ Ursache-Wirkungs-Analyse Mutationen-Test ε

diversifizierend

Zufallstest

Pertubationen-Test ε Back to Back-Test ε



Test spezieller Werte ✍ Grenzwertanalyse



Abbildung 8.1 : Klassifikation der Verfahren der analytischen Qualitätssicherung.



260

Kurseinheit IV

8.3 Kontrollflußbezogene Verfahren kontrollflußbezogene Verfahren

Kontrollflußbezogene dynamische Verfahren sind White Box-Verfahren. Anhand der (statischen) Kontrollstruktur des Programms (und ggf. der Programmspezifikation) werden Testfälle bestimmt und das Programm mit entsprechenden Testdaten ausgeführt. Das Programm wird also in erster Linie „gegen sich selbst“ und nur untergeordnet gegen seine Spezifikation getestet. 8.3.1 Kontrollflußgraphen Der Kontrollfluß strukturierter Programme wird durch Anweisungsfolgen, Selektion und Iteration gesteuert. Für kontrollflußbezogene Tests bereitet man den Quelltext üblicherweise graphisch auf, um den Kontrollfluß zu visualisieren. Häufig verwendet wird neben dem Programmablaufplan der Kontrollflußgraph, der eine abstraktere Sichtweise ermöglicht. Wir geben hierzu die folgende Definition an. Definition 8.3.1.1

Kontrollflußgraph

Zweig

Block eines Kontrollflußgraphen

Der Kontrollflußgraph eines Programms P ist ein gerichteter Graph G = (N, E, nstart , nfinal). N ist die Menge der Knoten, E ⊆ N × N die Menge der gerichteten Kanten, nstart ∈N der Startknoten, der keine Vorgänger besitzt, und nfinal ∈N der Endknoten, der keine Nachfolger hat. (Sehen wir von dem ausgezeichneten Endknoten ab, können wir einen Kontrollflußgraphen als verallgemeinerten Baum (vgl. Abschnitt 6.1) betrachten, der Zyklen aufweisen kann und in dem die Mengen der Nachfolger zweier unterschiedlicher Knoten nicht disjunkt sein müssen.) Der Startknoten nstart dokumentiert den Eintrittspunkt des Programms, das heißt den Beginn seines Anweisungsteils, und der Endknoten nfinal kanalisiert alle Austrittspunkte. Knoten ungleich nstart und nfinal stellen Programmzeilen dar, wobei wir unterstellen, daß der Programmtext den im Kurs vorgeschriebenen Layoutregeln genügt. Insbesondere beginnt jede Anweisung in einer neuen Zeile. Eine gerichtete Kante von einem Knoten i zu einem Knoten j, auch Zweig genannt, beschreibt einen statischen Kontrollfluß von i nach j. Ein Block (eines Kontrollflußgraphen) ist eine nicht-leere Folge von Knoten n1, ..., nk ∈ N \ {nstart , nfinal}, k ≥ 1, für deren zugehörige Folge von Programmzeilen z(n1), ..., z(nk) gilt: - Sie wird ausschließlich beginnend mit der ersten Zeile z(n1) ausgeführt. - Wird z(n1) ausgeführt, dann werden danach - falls k ≥ 2 - z(n2), ..., z(nk) in dieser Reihenfolge genau einmal ausgeführt. Für k = 1 wird nur z(n1) ausgeführt. - Die Anzahl k der Programmzeilen ist bezüglich der ersten beiden Eigenschaften maximal. Blöcke sind ein Hilfsmittel, um zu einer kompakteren Form des Kontrollflußgraphen zu gelangen, da alle Knoten eines Blocks zu einem einzigen Knoten reduziert werden können. Eine weitere Reduktion der Anzahl der Knoten erhalten wir, indem wir für Programmzeilen, die nur aus Schlüsselworten (z.B. begin, end, repeat, else, ...) bestehen, keinen eigenen Knoten vorsehen, sondern sie einem Nachbar-

261

8. Analytische Qualitätssicherung

knoten zuordnen. Ist diesem Nachbarknoten keine derartige Programmzeile (mit nur einem Schlüsselwort) zugeordnet, bricht das Verfahren ab. Anderenfalls wird das Verfahren iteriert. Außerdem werden Kommentarzeilen nicht berücksichtigt. Wir nennen einen Kontrollflußgraphen, dessen Knoten bzw. Blöcke nach obigen Regeln reduziert worden sind, einen kompakten Kontrollflußgraphen. Eine Folge aus Knoten und Blöcken, in der aufeinanderfolgende Elemente im Kontrollflußgraphen durch einen Zweig verbunden sind und die mit dem Startknoten beginnt und mit dem Endknoten endet, heißt Pfad (durch den Kontrollflußgraphen). ❑ Kontrollflußbezogene Verfahren zielen darauf ab, eine bestimmte Menge von Pfaden beim Test zu durchlaufen. Zu jedem Pfad erhalten wir einen (evtl. leeren) assoziierten Testfall, indem wir die (laut Programmspezifikation) zulässigen Eingabedaten auf genau solche einschränken, welche die Ausführung der dem Pfad zugeordneten Anweisungsfolge (und nur dieser) bewirken. Wir unterscheiden in Bezug auf die tatsächlich ausgeführten Pfade folgende Überdeckungskriterien:

kompakter Kontrollflußgraph

Pfad

assoziierter Testfall

Die Anweisungsüberdeckung (auch C0-Test) ist das einfachste Kriterium. Die Testfälle werden so gewählt, daß alle Knoten des Kontrollflußgraphen mindestens einmal besucht werden.

Anweisungsüberdekkung, C0-Test

Die Zweigüberdeckung (C1-Test) ist strenger als die Anweisungsüberdeckung. Es sind alle durch Selektion oder Iteration bedingten Verzweigungen im Kontrollfluß mindestens einmal zu verfolgen.

Zweigüberdeckung, C1-Test

Die Bedingungsüberdeckung zieht die Bedingungen in den Schleifen und Auswahlkonstrukten des zu testenden Programms zur Definition von Tests heran.

Bedingungsüberdekkung

Die Pfadüberdeckung nimmt unter den kontrollflußbezogenen Verfahren eine Sonderstellung ein. Sie stellt in ihrer vollständigen Form das umfassendste kontrollflußorientierte Kriterium dar, ist jedoch aufgrund der bereits in kleinen Programmen unpraktikabel hohen Anzahl von Testfällen kaum überprüfbar. Aus diesem Grund existieren Testverfahren, die sich der vollständigen Pfadüberdeckung auf unterschiedliche Arten annähern.

Pfadüberdeckung

Im folgenden stellen wir zu jedem Überdeckungskriterium ein konkretes Testverfahren vor. Beispiel 8.3.1.2 Als begleitendes Beispiel dient uns eine PASCAL-Funktion, der folgende Problembeschreibung zugrunde liegt. Gesucht ist eine Funktion, der als Parameter ein Ziffernstring übergeben wird, die den Wert der dadurch definierten (positiven) Zahl errechnet und diesen zurückliefert. Der Ergebnistyp ist real.

begleitendes Beispiel

262

Kurseinheit IV

Zur Definition eines Ziffernstrings geben wir folgende Syntaxdiagramme an: Ziffer

0 1 2 3 4 5 6 7 8 9

ganzZahl

Ziffer ganzZahlNichtLeer Ziffer

Dezimalbruch .

ganzZahl

ganzZahlNichtLeer

Ziffernstring

Dezimalbruch

ganzZahlNichtLeer

.

Beispiele für zulässige Ziffernstrings sind: 17.25

.25

17

17.

Beispiele für unzulässige Ziffernstrings sind: leere Eingabe

.

A.25

17.A

Nur ein Ziffernstring, der gemäß den obigen Syntaxdiagrammen aufgebaut ist, wird als korrekte Eingabe anerkannt und sein Wert berechnet. Jeder Ziffernstring, der nicht dieser Spezifikation entspricht, führt zur Rückgabe eines Fehlercodes.

8. Analytische Qualitätssicherung

Zur Bearbeitung des übergebenen Ziffernstrings werden die Funktionen length, liesZeichen und pruefeZeichen benutzt. length bestimmt die Länge einer Zeichenkette, liesZeichen liefert das Zeichen an der angegebenen Position einer Zeichenkette zurück und pruefeZeichen überprüft, ob das Zeichen an der angegebenen Position aus der Menge {’0’, ’1’, ’2’, ’3’, ’4’, ’5’, ’6’, ’7’, ’8’, ’9’} stammt. Außerdem setzen wir die Existenz der Funktion konvertiere voraus, die zur Konvertierung einer Ziffer (Typ char) in die zugehörige real-Zahl eingesetzt wird. Alle vier Funktionen sind bereits getestet bzw. verifiziert, so daß wir ihre Korrektheit voraussetzen können. In der untenstehenden entsprechenden PASCAL-Funktion werteZiffernfolgeAus haben wir auf Kommentare aus Platzgründen weitgehend verzichtet.1 function werteZiffernfolgeAus ( inZiffernString : string) : real; { liefert den Wert des uebergebenen Ziffernstrings als real-Zahl zurück, benutzt die Funktionen length, liesZeichen, pruefeZeichen und konvertiere } const FEHLERCODE = -1.0; type tWoBinIch = (vorDemKomma, nachDemKomma); tNatZahl = 0..maxint; var Zeichen : char; Wert, Genauigkeit : real; Position : tNatZahl; woBinIch : tWoBinIch; fehlerfrei : boolean; begin Wert := 0.0; Genauigkeit := 1.0; woBinIch := vorDemKomma; fehlerfrei := true; Position := 1; while (Position