Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
1 Einleitung
2 Die Programmiersprache Python
Teil I Einstieg in Python
3 Erste Schritte im interaktiven Modus
4 Der Weg zum ersten Programm
5 Kontrollstrukturen
6 Dateien
7 Das Laufzeitmodell
8 Funktionen, Methoden und Attribute
9 Informationsquellen zu Python
Teil II Datentypen
10 Das Nichts – NoneType
11 Operatoren
12 Numerische Datentypen
13 Sequenzielle Datentypen
14 Zuordnungen
15 Mengen
16 Collections
17 Datum und Zeit
18 Aufzählungstypen – Enum
Teil III Fortgeschrittene Programmiertechniken
19 Funktionen
20 Modularisierung
21 Objektorientierung
22 Ausnahmebehandlung
23 Iteratoren
24 Kontextobjekte
25 Manipulation von Funktionen und Methoden
Teil IV Die Standardbibliothek
26 Mathematik
27 Kryptografie
28 Reguläre Ausdrücke
29 Schnittstelle zu Betriebssystem und Laufzeitumgebung
30 Kommandozeilenparameter
31 Dateisystem
32 Parallele Programmierung
33 Datenspeicherung
34 Netzwerkkommunikation
35 Debugging und Qualitätssicherung
36 Dokumentation
Teil V Weiterführende Themen
37 Anbindung an andere Programmiersprachen
38 Distribution von Python-Projekten
39 Grafische Benutzeroberflächen
40 Python als serverseitige Programmiersprache im WWW – ein Einstieg in Django
41 Wissenschaftliches Rechnen
42 Insiderwissen
43 Von Python 2 nach Python 3
A Anhang
Stichwortverzeichnis

Download:
- Beispielprogramme, ca. 464 KB

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Python 3 von Johannes Ernesti, Peter Kaiser
Das umfassende Handbuch
Buch: Python 3

Python 3
Pfeil 35 Debugging und Qualitätssicherung
Pfeil 35.1 Der Debugger
Pfeil 35.2 Formatierte Bildschirmausgabe – pprint
Pfeil 35.3 Logdateien – logging
Pfeil 35.3.1 Das Meldungsformat anpassen
Pfeil 35.3.2 Logging Handler
Pfeil 35.4 Automatisiertes Testen
Pfeil 35.4.1 Testfälle in Docstrings – doctest
Pfeil 35.4.2 Unit Tests – unittest
Pfeil 35.5 Analyse des Laufzeitverhaltens
Pfeil 35.5.1 Laufzeitmessung – timeit
Pfeil 35.5.2 Profiling – cProfile
Pfeil 35.5.3 Tracing – trace
Pfeil 35.6 Optimierung
Pfeil 35.6.1 Die Optimize-Option
Pfeil 35.6.2 Mutabel vs. immutabel
Pfeil 35.6.3 Schleifen
Pfeil 35.6.4 Funktionsaufrufe
Pfeil 35.6.5 C
Pfeil 35.6.6 Lookups
Pfeil 35.6.7 Exceptions
Pfeil 35.6.8 Keyword Arguments
Pfeil 35.6.9 Alternative Interpreter: PyPy
 
Zum Seitenanfang

35.4    Automatisiertes Testen Zur vorigen ÜberschriftZur nächsten Überschrift

Pythons Standardbibliothek stellt zwei Module zur testgetriebenen Entwicklung (engl. test-driven development) bereit. Unter testgetriebener Entwicklung versteht man eine Art der Programmierung, bei der viele kleine Abschnitte des Programms, sogenannte Units, durch automatisierte Testdurchläufe auf Fehler geprüft werden. Bei der testgetriebenen Entwicklung wird das Programm nach kleineren, in sich geschlossenen Arbeitsschritten so lange verbessert, bis es wieder alle bisherigen und alle hinzugekommenen Tests besteht. Auf diese Weise können sich durch das Hinzufügen von neuem Code keine Fehler in alten, bereits getesteten Code einschleichen.

In Python ist das Ihnen möglicherweise bekannte Konzept der Unit Tests im Modul unittest implementiert. Das Modul doctest ermöglicht es, Testfälle innerhalb eines Docstrings, beispielsweise einer Funktion, unterzubringen. Im Folgenden werden wir uns zunächst mit dem Modul doctest beschäftigen, um danach zum Modul unittest voranzuschreiten.

 
Zum Seitenanfang

35.4.1    Testfälle in Docstrings – doctest Zur vorigen ÜberschriftZur nächsten Überschrift

Das Modul doctest erlaubt es, Testfälle innerhalb des Docstrings einer Funktion, Methode, Klasse oder eines Moduls zu erstellen, die beim Aufruf der im Modul doctest enthaltenen Funktion testmod getestet werden. Die Testfälle innerhalb eines Docstrings werden dabei nicht in einer neuen Definitionssprache verfasst, sondern können direkt aus einer Sitzung im interaktiven Modus in den Docstring kopiert werden.

[»]  Hinweis

Docstrings sind auch bzw. hauptsächlich für die Dokumentation beispielsweise einer Funktion gedacht. Aus diesem Grund sollten Sie die Testfälle im Docstring möglichst einfach und lehrreich halten, sodass der resultierende Docstring auch in Dokumentationen Ihres Programms verwendet werden kann.

Das folgende Beispiel erläutert die Verwendung des Moduls doctest anhand der Funktion fak, die die Fakultät einer ganzen Zahl berechnen und zurückgeben soll.

import doctest
def fak(n):
"""
Berechnet die Fakultaet einer ganzen Zahl.

>>> fak(5)
120
>>> fak(10)
3628800
>>> fak(20)
2432902008176640000

Es muss eine positive ganze Zahl uebergeben werden.

>>> fak(-1)
Traceback (most recent call last):
...
ValueError: Keine negativen Zahlen!
"""

res = 1
for i in range(2, n+1):
res *= i
return res
if __name__ == "__main__":
doctest.testmod()

Im Docstring der Funktion fak steht zunächst ein erklärender Text. Dann folgt, durch eine leere Zeile davon abgetrennt, ein Auszug aus dem interaktiven Modus von Python, in dem Funktionsaufrufe von fak mit ihren Rückgabewerten stehen. Diese Testfälle werden beim Ausführen des Tests nachvollzogen und entweder für wahr oder für falsch befunden.

Auf diese einfachen Fälle folgen, jeweils durch eine Leerzeile eingeleitet, ein weiterer erklärender Text sowie ein Ausnahmefall, in dem eine negative Zahl übergeben wurde. Beachten Sie, dass Sie den Stacktrace eines auftretenden Tracebacks im Docstring weglassen können. Auch die im Beispiel stattdessen geschriebenen Auslassungszeichen sind optional.

Der letzte Testfall wurde in der Funktion noch nicht berücksichtigt, sodass dieser im Test fehlschlagen wird. Um den Test zu starten, muss die Funktion testmod des Moduls doctest aufgerufen werden. Aufgrund der if-Abfrage

if __name__ == "__main__":
doctest.testmod()

wird diese Funktion immer dann aufgerufen, wenn die Programmdatei direkt ausgeführt wird. Der Test wird hingegen nicht durchgeführt, wenn die Programmdatei von einem anderen Python-Programm als Modul eingebunden wird. Im provozierten Fehlerfall lautet das Testresultat folgendermaßen:

*****************************************************************
File "fak.py", line 17, in __main__.fak
Failed example:
fak(-1)
Expected:
Traceback (most recent call last):
...
ValueError: Keine negativen Zahlen!
Got:
1
*****************************************************************
1 items had failures:
1 of 4 in __main__.fak
***Test Failed*** 1 failures.

Jetzt erweitern wir die Funktion fak dahingehend, dass sie im Falle eines negativen Parameters die gewünschte Exception wirft:

def fak(n):
"""
[…]
"""

if n < 0:
raise ValueError("Keine negativen Zahlen!")
res = 1
for i in range(2, n+1):
res *= i
return res

Durch diese Änderung werden bei erneutem Durchführen des Tests keine Fehler mehr angezeigt. Um genau zu sein: Es wird überhaupt nichts angezeigt. Das liegt daran, dass generell nur fehlgeschlagene Testfälle auf dem Bildschirm ausgegeben werden. Sollten Sie auch auf die Ausgabe geglückter Testfälle bestehen, starten Sie die Programmdatei mit der Option -v (für verbose).

Beachten Sie bei der Verwendung von Doctests, dass die in den Docstrings geschriebenen Vorgaben Zeichen für Zeichen mit den Ausgaben der intern ausgeführten Testfälle verglichen werden. Dabei sollten Sie stets im Hinterkopf behalten, dass die Ausgaben bestimmter Datentypen nicht immer gleich sind. So stehen beispielsweise die Schlüssel-Wert-Paare eines Dictionarys in keiner garantierten Reihenfolge. Darüber hinaus gibt es Informationen, die vom Interpreter oder anderen Gegebenheiten abhängen; beispielsweise entspricht die Identität einer Instanz intern ihrer Speicheradresse und wird sich deswegen natürlich beim Neustart des Programms ändern.

Eine weitere Besonderheit, auf die Sie achten müssen, ist, dass eine Leerzeile in der erwarteten Ausgabe einer Funktion durch den String <BLANKLINE> gekennzeichnet werden muss, da eine Leerzeile als Trennung zwischen Testfällen und Dokumentation fungiert:

def f(a, b):
"""
>>> f(3, 4)
7
<BLANKLINE>
12
"""

print(a + b)
print()
print(a * b)

Flags

Um einen Testfall genau an Ihre Bedürfnisse anzupassen, können Sie Flags vorgeben. Das sind Einstellungen, die Sie aktivieren oder deaktivieren können. Ein Flag wird in Form eines Kommentars hinter den Testfall im Docstring geschrieben. Wird das Flag von einem Plus (+) eingeleitet, wird es aktiviert, bei einem Minus (-) deaktiviert. Bevor wir zu einem konkreten Beispiel kommen, lernen Sie die drei wichtigsten Flags kennen.

Flag Bedeutung
ELLIPSIS Wenn dieses Flag gesetzt ist, kann die Angabe ... für eine beliebige Ausgabe einer Funktion verwendet werden. So können veränderliche Angaben wie Speicheradressen oder Ähnliches in größeren Ausgaben überlesen werden.
NORMALIZE_WHITESPACES Wenn dieses Flag gesetzt ist, werden Whitespace-Zeichen nicht in den Ergebnisvergleich einbezogen. Das ist besonders dann interessant, wenn Sie ein langes Ergebnis auf mehrere Zeilen umbrechen möchten.
SKIP Dieses Flag veranlasst das Überspringen des Tests. Das ist beispielsweise dann nützlich, wenn Sie im Docstring zu Dokumentationszwecken eine Reihe von Beispielen liefern, aber nur wenige davon bei einem Testlauf berücksichtigt werden sollen.

Tabelle 35.4    Doctest-Flags

In einem einfachen Beispiel erweitern wir den Doctest der bereits bekannten Fakultätsfunktion um die Berechnung der Fakultät einer relativ großen Zahl. Da es müßig wäre, alle Stellen des Ergebnisses im Doctest anzugeben, soll die Zahl mithilfe des Flags ELLIPSIS gekürzt angegeben werden.

import doctest
def fak(n):
"""
Berechnet die Fakultaet einer ganzen Zahl.

>>> fak(1000) # doctest: +ELLIPSIS
402387260077093773543702...000
>>> fak("Bla") # doctest: +SKIP
'BlubbBlubb'
"""

res = 1
for i in range(2, n+1):
res *= i
return res
if __name__ == "__main__":
doctest.testmod()

Das Setzen der Flags wurde fett hervorgehoben. Wie Sie sehen, umfasst das Beispiel einen zweiten – offensichtlich fehlschlagenden – Test, bei dem aber das SKIP-Flag gesetzt wurde. Deshalb wird ein Testlauf hier keinen Fehler feststellen.

Bleibt noch zu sagen, dass insbesondere die Funktion testmod eine Fülle von Möglichkeiten bietet, die Testergebnisse im Programm zu verwenden oder den Prozess des Testens an Ihre Bedürfnisse anzupassen. Sollten Sie daran interessiert sein, bietet sich die Python-Dokumentation an, in der die Funktion besprochen wird.

 
Zum Seitenanfang

35.4.2    Unit Tests – unittest Zur vorigen ÜberschriftZur nächsten Überschrift

Das zweite Modul zur testgetriebenen Entwicklung heißt unittest und ist ebenfalls in der Standardbibliothek enthalten. Das Modul unittest implementiert die Funktionalität des aus Java bekannten Moduls JUnit, das den De-facto-Standard zur testgetriebenen Entwicklung in Java darstellt.

Der Unterschied zum Modul doctest besteht darin, dass die Testfälle bei unittest außerhalb des eigentlichen Programm-Codes in einer eigenen Programmdatei in Form von regulärem Python-Code definiert werden. Das vereinfacht die Ausführung der Tests und hält die Programmdokumentation sauber. Umgekehrt ist mit dem Erstellen der Testfälle allerdings mehr Aufwand verbunden.

Um einen neuen Testfall mit unittest zu erstellen, müssen Sie eine von der Basisklasse unittest.TestCase abgeleitete Klasse erstellen, in der einzelne Testfälle als Methoden implementiert sind. Die folgende Klasse implementiert die gleichen Testfälle, die wir im vorangegangenen Abschnitt mit dem Modul doctest durchgeführt haben. Dabei muss die zu testende Funktion fak in der Programmdatei fak.py implementiert sein, die von unserer Test-Programmdatei als Modul eingebunden wird.

import unittest
import fak
class MeinTest(unittest.TestCase):
def testBerechnung(self):
self.assertEqual(fak.fak(5), 120)
self.assertEqual(fak.fak(10), 3628800)
self.assertEqual(fak.fak(20), 2432902008176640000)
def testAusnahmen(self):
self.assertRaises(ValueError, fak.fak, -1)
if __name__ == "__main__":
unittest.main()

Es wurde eine Klasse namens MeinTest erzeugt, die von der Basisklasse unittest.TestCase erbt. In der Klasse MeinTest wurden zwei Testmethoden namens testBerechnung und testAusnahmen implementiert. Beachten Sie, dass der Name solcher Testmethoden mit test beginnen muss, damit sie später auch tatsächlich zum Testen gefunden und ausgeführt werden.

Innerhalb der Testmethoden werden die Methoden assertEqual bzw. assertRaises verwendet, die den Test fehlschlagen lassen, wenn die beiden angegebenen Werte nicht gleich sind bzw. wenn die angegebene Exception nicht geworfen wurde.

Um den Testlauf zu starten, wird die Funktion unittest.main aufgerufen. Die Fallunterscheidung

if __name__ == "__main__":
unittest.main()

bewirkt, dass der Unit Test nur durchgeführt wird, wenn die Programmdatei direkt ausgeführt wird, und ausdrücklich nicht, wenn die Programmdatei als Modul in ein anderes Python-Programm importiert wurde. Die aufgerufene Funktion unittest. main erzeugt, um den Test durchzuführen, Instanzen aller Klassen, die im aktuellen Namensraum existieren und von unittest.TestCase erben. Dann werden alle Methoden dieser Instanzen aufgerufen, deren Namen mit test beginnen.

Die Ausgabe des Beispiels lautet im Erfolgsfall:

..
-----------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Dabei stehen die beiden Punkte zu Beginn für zwei erfolgreich durchgeführte Tests. Ein fehlgeschlagener Test würde durch ein F gekennzeichnet.

Im Fehlerfall wird die genaue Bedingung angegeben, die zum Fehler geführt hat:

.F
=================================================================
FAIL: testBerechnung (__main__.MeinTest)
-----------------------------------------------------------------
Traceback (most recent call last):
File "testen.py", line 7, in testBerechnung
self.assertEqual(fak.fak(5), 12)
AssertionError: 120 != 12

-----------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Die Klasse TestCase erlaubt es zusätzlich, die parameterlosen Methoden setUp und tearDown zu überschreiben, die vor bzw. nach den Aufrufen der einzelnen Testfunktionen ausgeführt werden. In diesen Funktionen können also Initialisierungs- und Deinitialisierungsoperationen implementiert werden. Exceptions, die in setUp oder tearDown geworfen werden, lassen den jeweils aktuellen Test fehlschlagen.

Grundlegende Testmethoden

Aus den vorangegangenen Beispielen kennen Sie bereits die Assert-Methoden assertEqual und assertRaises, mithilfe derer der einem Test zugrunde liegende Vergleich implementiert wird. Die Klasse TestCase definiert eine ganze Reihe solcher Methoden, die im Folgenden zusammengefasst werden.

Die Methoden verfügen alle über den optionalen Parameter msg, für den eine Fehlerbeschreibung angegeben werden kann, die im Falle eines fehlschlagenden Tests ausgegeben wird. Dieser Parameter wurde aus Gründen der Übersichtlichkeit in Tabelle 35.5 ausgelassen.

Methode Testet auf
assertEqual(first, second) first == second
assertNotEqual(first, second) first != second
assertTrue(expr) bool(expr) is True
assertFalse(expr) bool(expr) is False
assertIs(first, second) first is second
assertIsNot(first, second) first is not second
assertIsNone(expr) expr is None
assertIsNotNone(expr) expr is not None
assertIn(first, second) first in second
assertNotIn(first, second) first not in second
assertIsInstance(obj, cls) isinstance(obj, cls)
assertNotIsInstance(obj, cls) not isinstance(obj, cls)
assertGreater(first, second) first > second
assertGreaterEqual(first, second) first >= second
assertLess(first, second) first < second
assertLessEqual(first, second) first <= second

Tabelle 35.5    Methoden der Klasse TestCase

Testen auf Exceptions

Die Klasse TestCase enthält die Methoden assertRaises und assertWarns, die verwendet werden können, um zu testen, ob Funktionen Exceptions bzw. Warnungen werfen. Sie können mit einer funktionalen Schnittstelle verwendet werden:

assertRaises(exc, fun, *args, **kwds)

Dabei wird getestet, ob das Funktionsobjekt fun bei der Ausführung mit den Parametern args und kwargs eine Exception vom Typ exc wirft.

Alternativ können sowohl assertRaises als auch assertWarns ein Kontextobjekt erzeugen:

with self.assertRaises(TypeError):
pass

Der Vorteil dieser Schreibweise ist, dass der zu testende Code nicht extra in eine Funktion gekapselt werden muss.

Testen auf reguläre Ausdrücke

Zum Prüfen von Strings existieren die Methoden assertRegex und assertNotRegex, denen die Parameter text und regex übergeben werden. Ein Aufruf einer dieser Funktionen prüft, ob text auf den regulären Ausdruck regex passt bzw. nicht passt. Der reguläre Ausdruck regex kann sowohl als String als auch als RE-Objekt übergeben werden.

self.assertRegex("Test", r"Te.t")

Analog dazu existieren die Methoden assertWarnsRegex und assertRaisesRegex, die wie ihre Pendants aus dem vorangegangenen Abschnitt funktionieren, aber zusätzlich den Text der geworfenen Exception gegen einen regulären Ausdruck prüfen. Der reguläre Ausdruck wird als zweiter Parameter übergeben:

with self.assertRaises(TypeError, r"."):
pass

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Python 3 Python 3
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Einstieg in Python
Einstieg in Python


Zum Rheinwerk-Shop: Python. Der Grundkurs
Python. Der Grundkurs


Zum Rheinwerk-Shop: Algorithmen mit Python
Algorithmen mit Python


Zum Rheinwerk-Shop: Objektorientierte Programmierung
Objektorientierte Programmierung


Zum Rheinwerk-Shop: Raspberry Pi. Das umfassende Handbuch
Raspberry Pi. Das umfassende Handbuch


Zum Rheinwerk-Shop: Roboter-Autos mit dem Raspberry Pi
Roboter-Autos mit dem Raspberry Pi


Zum Rheinwerk-Shop: Neuronale Netze programmieren mit Python
Neuronale Netze programmieren mit Python


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2020
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern