Objektorientierte Programmierung OOP
KE5 Ausnahmen und Testen; Begriffe, Beispiele und Definitionen
KE5 Ausnahmen und Testen; Begriffe, Beispiele und Definitionen
-
- 1 / 39
-
Lernkarten
Begriff: Ausnahmen in Java
Java bietet Sprachkonstrukte, um mit Laufzeitfehlern konstruktiv umzugehen. Sie ermöglichen es,
• das Auftreten von Ausnahmesituationen zu überwachen,
• im Fall einer Ausnahmesituation, bei deren Auftreten der normale Verlauf der Programmabarbeitung unterbrochen wird, vom Laufzeitsystem ein Ausnahme-Objekt erzeugen zu lassen und
• die Kontrolle an spezielle Programmteile zu übergeben, die versuchen, den Laufzeitfehler aufzufangen (engl. catch) und zu behandeln.
Aufgabe: Welche Aufgabe haben Exceptions?
Exemplare der Klasse Exception oder einer Unterklasse sind behandelbare Ausnahmen, wie zum Beispiel Probleme beim Ein- und Ausgabe (IOException). Bei einer solchen Ausnahme soll das Programm nicht einfach abgebrochen werden, sondern es soll wenn möglich eine Lösung für das Problem gefunden werden. Lässt sich das Problem nicht beheben, so sollte eine entsprechende Mitteilung an den Benutzer erfolgen und das Programm ordentlich, d. h. mit einer return-Anweisung und nicht mit einer Ausnahme, beendet werden.
Hinweis: In Java können eigene Ausnahme erstellt werden.
Ein Programmierer kann zusätzlich zu den bestehenden Ausnahmeklassen auch eigene Typen definieren. Eigene Ausnahmeklassen sind im Normalfall Unterklassen von Exception.
public class MeineAusnahme extends Exception { }
Definition: throw-Anweisung
Definition: throw-Anweisung Eine setzt sich aus dem Schlüsselwort throw und einer Refe- throw-Anweisung renz auf ein Ausnahmeobjekt, also ein Objekt vom Typ Throwable zusammen. [JLS: § 14.18] throw Ausnahmeobjekt; Häufig wird erst in der Anweisung mit Hilfe eines Konstruktoraufrufs ein neues Ausnahmeobjekt erzeugt.
Definition: throw-Anweisung im Methodenkopf
Definition 28.2-2: throws im Methodenkopf Alle Ausnahmen, die eine Methode werfen kann, werden mit Hilfe des Schlüsselwortes throws nach der Parameterliste im Methodenkopf angegeben. [JLS: § 8.4.6, § 8.8.5] Modifikatoren Ergebnistyp Name(Parameterliste) throws Ausnahmetypenliste { } Die Ausnahmetypliste besteht aus mindestens einem Ausnahmetyp. Weitere Ausnahmetypen können durch Kommata getrennt angegeben werden.
Beispiel: throw-Anweisung in getter-Methode
public class Rechnung {
private double rabatt;
// ...
void legeRabattFest(final double neuerRabatt)
throws UngueltigerRabattAusnahme {
if(neuerRabatt < 0 || neuerRabatt > 1) {
throw new UngueltigerRabattAusnahme(neuerRabatt); }
this.rabatt = neuerRabatt;
}
}
Beispiel: vordefinierte Ausnahme
public class Rechnung {
private double rabatt;
// ...
void legeRabattFest(final double neuerRabatt) {
if(neuerRabatt < 0 || neuerRabatt > 1) {
throw new IllegalArgumentException( "Dieser Rabatt ist
ungueltig: " + neuerRabatt);
}
this.rabatt = neuerRabatt;
}
}
Bemerkung: mehrere Ausnahmen in einer Methode
Kann eine Methode mehrere verschiedene Ausnahmen werfen, so können alle Typen einzeln oder nur entsprechende Oberklassen angegeben werden. Es ist besser, alle Ausnahmen explizit zu erwähnen, da so der Aufrufer auch geeignet auf jeden Typ reagieren kann. Würden wir zwei Spezialisierungen von UngueltigerRabattAusnahme defi- nieren, um zwischen negativen und zu hohen Rabatten unterscheiden zu können, so könnten im Methodenkopf entweder UngueltigerRabattAusnahme oder die beiden Spezialisierungen, mit Komma getrennt, aufgeführt werden.
public class NegativerRabattAusnahme extends UngueltigerRabattAusnahme {
public NegativerRabattAusnahme(double rabatt) {
super(rabatt);
}
}
public class ZuHoherRabattAusnahme extends
UngueltigerRabattAusnahme { public
ZuHoherRabattAusnahme(double rabatt) {
super(rabatt);
}
}
public class Rechnung {
private double rabatt;
// ...
void legeRabattFest(final double neuerRabatt) throws
NegativerRabattAusnahme, ZuHoherRabattAusnahme
{
if(neuerRabatt < 0) {
throw new NegativerRabattAusnahme(neuerRabatt);
}
if (neuerRabatt > 1) {
throw new ZuHoherRabattAusnahme(neuerRabatt);
}
this.rabatt = neuerRabatt;
}
}
Definition: try-Anweisung
Eine try-Anweisung besteht aus drei verschiedenen Bestandteilen. Die kritischen Anweisungen, wie zum Beispiele Aufrufe an Methoden, die Ausnahmen erzeugen können, werden in einen try-Block eingeschlossen. Nach diesem muss entweder mindestens ein catch-Abschnitt oder ein finally-Block folgen. Mit Hilfe eines catch-Abschnitts können im try-Block aufgetretene Ausnahmen gefangen und behandelt werden. Im catch-Abschnitt wird angegeben, für welche Ausnahmetypen dieser Abschnitt zuständig ist. Sollen mehrere verschiedene Ausnahmetypen unterschiedliche behandelt werden, so können mehrere catchAbschnitte folgen. Die Anweisungen im finally-Block werden immer am Ende der gesamten try-Anweisung ausgeführt. Hier werden in der Regel diverse Aufräumarbeiten erledigt. [JLS: § 14.20]
try {
// Kritische Anweisungen
} catch (Ausnahmetyp1 name1) {
// Diese Anweisungen werden ausgeführt, wenn
// eine Ausnahme vom Typ Ausnahmetyp1 aufgetreten
ist.
// Die konkrete Ausnahme wird von der Variablen
// name1 referenziert.
} catch (Ausnahmetyp2 name2) {
// analog zum vorhergehenden Abschnitt
} finally {
// Diese Anweisungen werden immer ausgeführt
}
Passen mehrere catch-Abschnitte zu der geworfenen Ausnahme, so wird immer nur der erste passende ausgeführt.
Beispiel: try-Ausnahme
Bemwekung: Klasse Error
Ein Objekt vom Typ Error ist im Normalfall ein sehr schwerwiegender Fehler, bei dem das Programm nur noch beendet werden kann. Beispiele dafür sind der StackOverflowError und der OutOfMemoryError, beides Unterklassen der Klasse VirtualMachineError. Exemplare der Klasse Error oder einer ihrer Unterklassen sollten somit nicht behandelt werden. Bei Ausnahmen vom Typ Error handelt es sich wie bei Ausnahmen vom Typ RuntimeException um nicht zu berücksichtigende Ausnahmen, so dass diese ebenfalls nicht mit throws im Methodenkopf angegeben werden müssen. [JLS: § 11.2.4]
Aufgabe: Wie kann verhindertwerden, das eine aufrufende Methode die Ausnahme nicht behandeln, so das, sie diese auch an ihren Aufrufer weiterteitet.
Dazu muss die Methode lediglich die Ausnahme ebenfalls in ihrem Methodenkopf mit Hilfe von throws angeben. Eine Ausnahme wird solange an die Aufrufer weitergereicht, bis eine Methode diese behandelt oder bis die main()-Methode erreicht wurde. In diese Falle bekommt der Benutzer die Ausnahme direkt angezeigt. Dass eine Ausnahme bis zum Benutzer gelangt, sollte in der Regel durch geeignete Behandlungen vermieden werden, da so dem Benutzer der Fehler besser erläutert werden kann.
public class Blumenladen {
public void ausnahmeWeiterreichen() throws NegativerRabattAusnahme, ZuHoherRabattAusnahme {
Rechnung r = new Rechnung();
double rabatt = 0.2; r.setRabatt(rabatt);
}
}
Bedeutung: getStackTrace() und printStackTrace()
Ausnahmen bieten noch weitere hilfreiche Informationen. So kann mit Hilfe der Methode getStackTrace() an einem Ausnahmeobjekt erfragt werden, in wel- getStackTrace() cher Methode es geworfen und durch welche Methoden es weitergereicht wurde. Alternativ kann auch die Methode printStackTrace() genutzt werden, die den gesamten Methodenstapel auf der Fehlerausgabe ausgibt.
Aufgabe: Welche Bedeutung und Vorteile bieten Ausnahmen?
Ausnahmen bieten die Möglichkeit, Programme robuster zu machen und auf fehlerhafte Eingaben oder andere Probleme geeignet zu reagieren. Wir können unabhängig vom Ergebnistyp Fehler anzeigen, entsprechende Informationen mitgeben und auf die Ausnahmen geeignet reagieren.
Java-Konventionen: Kommentare
Kommentare sollten über das ganze Programm verteilt sein und Information enthalten, die für das Lesen und Verstehen eines Programms von Bedeutung sind.
Begriff: javadoc
Die Java-Umgebung enthält das Dienstprogramm javadoc2 , das dazu dient, Re- javadoc ferenzdokumentationen im HTML-Format aus den Java-Quelltexten zu erzeugen. Die Java-API-Referenz3 , die wir im Zusammenhang mit Zeichenketten (Kapitel 26) kennen gelernt haben, ist eine auf diese Weise generierte Referenzdokumentation der Java-Standardbibliothek.
Java-Konventionen: private Programmteile
Die Dokumentation einer Klasse sollte alle nicht-privaten Elemente dieser Klasse auflisten.
Begriff: statischer Test
Bei einem statischen Test wird das Programm nicht ausgeführt, sondern einer Analyse unterzogen. Auch hier gibt es wieder eine Vielzahl von Techniken und Verfahren, eine davon ist auch das Übersetzen des Programms, wodurch es auf syntaktische Fehler untersucht wird. Alternativ kann der Quelltext auch inspiziert werden.
Begriff: dynamischer Test
Bei einem dynamischen Test wird das Programm mit vorher festgelegten Eingabedaten ausgeführt und anschließend das tatsächliche Verhalten mit dem erwarteten Verhalten verglichen.
Definition: dynamischer Test
Dynamisches Testen bedeutet, ein Programm(-teil) stichprobenartig auszuführen, um Unterschiede zwischen seinem erwarteten und tatsächlichen Verhalten zu finden.
Begriff: Verifikation
Bei der Verifikation wird im Gegensatz zum Testen mit mathematischen Verfahrenbewiesen, dass der Quelltext mit dem formalen Modell oder der formalen Spezifikation übereinstimmt.
Bemerkung: Testen gegen Verifizieren
Ein Test kann im Allgemeinen keine vollständigen Aussagen über die Korrektheit eines Programms liefern. Eine Ausnahme bildet das erschöpfende Testen, bei dem alle möglichen Testfälle durchgespielt werden. Dies ist aber in den wenigsten Anwendungsfällen möglich. In der Regel kann Testen nur als Stichprobenverfahren eingesetzt werden. Ein Test kann nur die Anwesenheit von Fehler zeigen und nicht ihre Abwesenheit. Ein Test ist demnach erfolgreich, wenn Fehler gefunden werden. Ein Test, der keinen Unterschied zwischen erwartetem und tatsächlichem Verhalten aufdeckt, bietet keine neuen Informationen. Verifizierende Verfahren können allgemeine Aussagen über Korrektheit und andere Programmeigenschaften erzeugen. Sie sind für realistische Anwendungen aber meist nicht anwendbar, weil die formale Spezifikation der Anforderungen zu zeitaufwändig wäre oder weil die Komplexität der mathematischen Verfahren Grenzen setzt.
Aufgaben: nennen Sie die möglichen Teststufen.
Komponententest, Integrationstest, Systemtest, Teststufen Abnahmetest und Regressionstest
Aufgabe: Beschreiben Sie den Komponententest
Beim Komponententest wird eine kleine, abgeschlossene Einheit, zum Beispiel eine Komponententest Klasse, getestet. Deshalb wird dieser Test auch Modul- oder Klassentest genannt.
Aufgabe: Beschreiben Sie den Integrationstest.
Das Zusammenspiel der vorher getesteten Komponenten wird mit Hilfe des Integrationstest Integrationstests überprüft. Hierbei können zum Beispiel Fehler in Schnittstellen gefunden werden.
Aufgabe: was wir beim Systemtest geprüft.
Ob wirklich das gesamte System die Anforderungen erfüllt und alle Elemente richtig zusammenarbeiten, wird mit Hilfe des Systemtests untersucht.
Aufgabe: von wem und wo wird der Abnahmetest durchgeführt?
Die bisherigen Tests werden in der Regel vom Hersteller durchgeführt. Der Abnahmetest hingegen wird im Bei sein des Kunden oder von ihm selbst durchgeführt. Hierbei wird auch oft die Akzeptanz der Benutzer analysiert.
Aufgabe: wann wird ein Regressionstest durchgeführt?
In der Regel wird Software nicht nur einmal entwickelt, sondern später erweitert, angepasst oder es werden später aufgedeckte Fehler behoben. Da sich bei der Überarbeitung und Veränderung des Quelltextes auch wieder neue Fehler einschleichen können, sollten nach jeder Änderung sogenannte Regressionstests durchgeführt werden.
Beschreibung: nicht funktionale Anforderungen und funktionale Anforderungen
Ergänzend zu den Teststufen können auch noch verschiedene Arten von Tests zum Einsatz kommen. So kann man sowohl funktionale als auch nicht funktionale Anforderungen testen. Dabei beschreiben funktionale Anforderungen das erwünschte sichtbare Verhalten des Systems. Nicht funktionale Anforderungen beschreiben, wie gut und mit welcher Qualität dieses Verhalten erreicht werden soll. Zu nicht funktionalen Anforderungen zählen beispielsweise Effizienz, Zuverlässigkeit und Benutzbarkeit.
Beschreibung: struktureller Test (Whitebox-Verfahren) und Spezifikation oder Anforderungen Test (Blackbox-Verfahren)
So kann die Struktur des Quelltextes berücksichtigt werden, in diesem Fall spricht man von einem strukturellen Test oder Whitebox-Verfahren. Wird die Struktur nicht berücksichtigt, sondern lediglich die Blackbox-Verfahren Spezifikation oder Anforderungen, so spricht man von Blackbox-Verfahren.
Inhalt: Ein wichtiger Bestandteil eines Testplans bzw. einer Teststrategie ist die Testspezifikation. Welche Angaben sind in der Testspezifikation enthalten?
- Eingabedaten
- Vorbedingungen und
- das erwartete Verhalten.
Begriff: positiver und negativer Test
Der positive Test beschreib den Testfall in dem mit erlaubten Werten getestet wird. Beim negativ Test werden nicht zulässige Werte verwendet, damit geprüft, wie sich ein Progarmmcode bei Ausnahmen verhält.
Dokumentation: Testdurchführung
Die Testdurchführung wird in Form eines Testprotokolls dokumentiert. Es enthält Informationen darüber, welcher Testfall mit welchen Ergebnissen durchgeführt wurde. Nach der Ausführung muss überprüft werden, ob es Abweichungen vom erwarteten Verhalten gibt. Im Falle einer Abweichung muss dann die Ursache für den Fehler gefunden und später behoben werden.
Begriff: dynamischer Test
Beim dynamischen Testen von Klassen wird jede Methode der Klasse systematisch im Bezug auf ihre Spezifikation stichprobenartig untersucht. Die Spezifikation kann, wie wir an den vielen Programmbeispielen und Selbsttestaufgaben sahen, informell vorgegeben sein; sie kann aber auch mit mathematischer Präzision formuliert sein.
Java-Konventionen: Testen
Wichtig beim Testen ist, dass gegen die Spezifikation und nicht gegen die Implementierung getestet wird.
Aufgabe: welche Schritte sollten beim testen durchgeführt werden.
• Neue Objekte mit Hilfe der verfügbaren Konstruktoren erzeugen und initialisieren,
• überprüfen, ob die erzeugten Objekte den erwarteten Zustand aufweisen,
• jede Methode mit geeigneten Argumenten sowie
• kritische Kombinationen von Objektzustand und Methodenaufruf testen und
• mögliche Ausnahmesituationen überprüfen
Bedeutung: JUnit
JUnit ist ein Rahmenwerk und Testwerkzeug, das die Erstellung von Testfällen, das Ausführen und Wiederholen von Tests sowie die Auswertung von Testfällen systematisch unterstützt. JUnit wurde von Kent Beck und Erich Gamma 1998 auf der Grundlage von SUnit4 , einer Testumgebung für Smalltalk, entwickelt. Die Grundidee, die auch in JUnit einging, stammt von Kent Beck, der 1994 das Konzept der Testmuster und die zugehörige Testumgebung entwickelte5