Level 06: Tests

Level 6: Tests #

Vorher oder nachher? #

In der Softwareentwicklung spielen Tests eine wichtige Rolle. Unittests testen dabei gezielt einzelne Funktionen. So soll sichergestellt werden, dass möglichst wenige Fehler in der Funktion enthalten sind. Bei Unittests werden nicht alle möglichen Fälle abgeprüft. Dafür wird versucht, möglichst typische Fälle sowie Randfälle, die besonders behandelt werden müssen, zu finden.

Wenn Tests erst nach dem Erstellen des Programmes geplant werden, fallen sie als unnützer Overhead (zusätzliche Arbeit) oft weg.

Test-Driven-Development dreht den Spieß um. Hier werden zunächst die Tests geschrieben, und dann erst das Programm! So wird sichergestellt, dass nur genau das programmiert wird, was gebraucht wird.

In Python integriert sind zwei sehr einfache Möglichkeiten, solche Tests umzusetzen.

Tests im Docstring #

Docstrings (die langen Kommentare, die durch drei doppelte Anführungszeichen markiert werden) dienen der (auch automatisierten) Dokumentation. Da Testfälle oft gut zeigen, wie eine Funktion arbeitet, ist es naheliegend, solche (getrennt durch eine Leerzeile) einfach in den Docstring zu schreiben. Importiert werden muss das Modul doctest, aufgerufen wird daraus die Funktion `testmod()``, wenn die Datei ausgeführt wird. Die Tests sehen dabei aus, wie Funktionsaufrufe im REPL. Solange du erst die Tests schreibst, sind das allerdings eher so etwas wie Erwartungen, die du einfach sehr genau aufschreibst.

Übung: der Palindrom-Checker #

Sieh Dir hier die Umsetzung eines Docstring-Tests an. Lies zunächst das Programm und lass es danach laufen!

import doctest

def ist_palindrom(wort):
    """
    Ein Palindrom ist ein Wort oder Satz, das/der von vorne wie von hinten
    gelesen gleich ist. Die Funktion soll `True` zurückgeben, wenn die
    Eingabe ein Palindrom ist, und `False`, falls nicht. Leer- und Satzzeichen sowie
    der Unterschied zwischen Klein- und Großbuchstaben sollen ignoriert
    werden.
    
    >>> ist_palindrom('Uhu')
    True
    >>> ist_palindrom('Anna')
    True
    >>> ist_palindrom('Kaffee')
    False
    >>> ist_palindrom('A man, a plan, a canal: Panama')
    True
    """
    # Wie müsste eine solche Funktion aussehen?
    return True
    
if __name__ == '__main__':
    doctest.testmod()

Versuche, die Funktion wie gefordert zu implementieren!

Tipp 1

Wie gehst du selbst vor, um zu sehen, ob ein Wort ein Palindrom ist? Vergleichst du den ersten mit dem letzten, den zweiten mit dem vorletzten Buchstaben, und so weiter? Das lässt sich auf verschiedene Arten umsetzen!

Als Hilfe oder Anregung könnte das Kapitel zu String-Methoden aus dem Rheinwerk-OpenBook dienen.

Tipp 2

Wenn wir den Testfall »A man, a plan, a canal: Panama« ansehen, können wir die Satzzeichen und die großgeschriebenen Buchstaben ignorieren. Eine Funktion, die das für uns erledigt, könnte hilfreich sein:

def nur_kleinbuchstaben(wort):
    """
    Wandelt Großbuchstaben aus einem Eingabestring in
    Kleinbuchstaben um und entfernt alle Zeichen, die
    nicht Buchstaben sind.
    
    >>> nur_kleinbuchstaben('HALLO')
    'hallo'
    >>> nur_kleinbuchstaben('Hallo Python!')
    'hallopython'
    >>> nur_kleinbuchstaben('»Weißt du«, sagte er, »es geht mir gut!«')
    'weißtdusagteeresgehtmirgut'
    """
    ret_wort = ''
    for buchstabe in wort:
        if buchstabe.isalpha():
            ret_wort += buchstabe.lower()
    return ret_wort
Tipp 3

Wörter, die keine Buchstaben haben, oder einzelne Buchstaben sind von sich aus schon Palindrome, oder? Von vorne wie von hinten gelesen sehen wir die gleichen Buchstaben…

Daher lässt sich für solche Eingabestrings eine kompliziertere Behandlung sparen. Die Struktur deiner Funktion könnte z.B. so aussehen:

ist_pal = True
# …
if len(wort) > 1:
    # …hier soll Code stehen, der ist_pal auf
    # False setzt, falls kein Palindrom vorliegt…
return ist_pal
Tipp 4

Weil du vorher hoffentlich ausgeschlossen hast, dass du leere Zeichenketten untersuchst (siehe Tipp 3), kannst du einen Index berechnen, der 1 von der Länge des Wortes abzieht.

Dieser Index beginnt vom Ende des Wortes nach Vorne zu wandern, ein anderer Index wandert von Vorne nach Hinten.

Werden dabei Paare von Buchstaben gefunden, die nicht gleich sind, ist das Wort kein Palindrom.

(Voraussetzung ist, dass keine Leer- oder Satzzeichen mehr in der Zeichenkette vorhanden sind, und dass nicht Groß- mit Kleinbuchstaben verglichen werden.)

von_vorne = 0
von_hinten = laenge_wort - 1
while von_vorne < von_hinten:
    if not wort[von_vorne] == wort[von_hinten]:
        ist_pal = False
        break
    von_vorne += 1
    von_hinten -= 1
Lösungsvorschlag
import doctest

def ist_palindrom(wort):
    """
    Ein Palindrom ist ein Wort oder Satz, das/der von vorne wie von hinten
    gelesen gleich ist. Die Funktion soll `True` zurückgeben, wenn die
    Eingabe ein Palindrom ist, und `False`, falls nicht. Leer- und Satzzeichen sowie
    der Unterschied zwischen Klein- und Großbuchstaben sollen ignoriert
    werden.
    
    >>> ist_palindrom('Uhu')
    True
    >>> ist_palindrom('Anna')
    True
    >>> ist_palindrom('Kaffee')
    False
    >>> ist_palindrom('A man, a plan, a canal: Panama')
    True
    """
    ist_pal = True
    nur_klein = nur_kleinbuchstaben(wort)
    laenge_wort = len(nur_klein)
    if laenge_wort > 1:
        von_vorne = 0
        von_hinten = laenge_wort - 1
        while von_vorne < von_hinten:
            if not nur_klein[von_vorne] == nur_klein[von_hinten]:
                ist_pal = False
                break
            von_vorne += 1
            von_hinten -= 1
    return ist_pal

def nur_kleinbuchstaben(wort):
    """
    Wandelt Großbuchstaben aus einem Eingabestring in
    Kleinbuchstaben um und entfernt alle Zeichen, die
    nicht Buchstaben sind.
    
    >>> nur_kleinbuchstaben('HALLO')
    'hallo'
    >>> nur_kleinbuchstaben('Hallo Python!')
    'hallopython'
    >>> nur_kleinbuchstaben('»Weißt du«, sagte er, »es geht mir gut!«')
    'weißtdusagteeresgehtmirgut'
    """
    ret_wort = ''
    for buchstabe in wort:
        if buchstabe.isalpha():
            ret_wort += buchstabe.lower()
    return ret_wort
    
if __name__ == '__main__':
    doctest.testmod()

Zurück Weiter