Tests & Dokumentation

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Hallo,

langsam aber sicher komme ich mit meinen Skripts in einen Bereich, wo Änderungen an einem Skript Einfluss auf andere Skripte haben. Irgendwas mal schnell geändert, und *puff* - schon funktioniert die Anwendung nicht mehr, die das Skript mitbenutzt.
Um solche Probleme zu umgehen, wollte ich jetzt mich in das Testen von Code einfuchsen. Bei Python selbst ist ja doctest und unittest dabei. Beim EGG von SQLAlchemy ist ein nose - Ordner dabei, wieder ein Testframework. Und es gibt sicherlich noch mehr. Jetzt wollte ich mal fragen, was denn hier so im Forum verwendet wird? Gefühlsmäßig würde ich zu Pythons unittest greifen, oder ist unittest nicht mehr aktuell, weil was besseres existiert?

Jetzt habe ich noch einige Fragen zur Handhabung von Tests: Wie geht man mit mit Tests um, die z.B. Datenbank - Funktionen testen? Wie teste ich, ob die Row, die mir meine Funktion zurückgibt, auch dem entspricht, was ich haben möchte? Nehmen wir an, ich will einen Reader für OpenStreetMap - Daten aus einer Datenbank schreiben: Für die Tests kann ich ja jetzt nicht vorraussetzen, dass eine OpenStreetMap - Datenbank vorhanden ist, d.h. ich müsste da ja was Erzeugen, was sich wie die Datenbank verhält und auch noch den richtigen Inhalt hat, oder?

Wie testet man, ob z.B. die XML-Repräsentation eines OSM-Datensatzes dem entspricht, was ausgelesen werden sollte? Mittels Zeichenketten-Vergleich? Da kann es doch eigentlich nicht auf Leerzeichen und Umbrüche ankommen, sondern nur das Markup sollte getestet werden ...
Oder wenn ich eine Bibliothek wie OGR (siehe auch hier) verwende, die mir beim Lesen einer Datei ein Layer oder Feature oder was auch immer zurückgibt, wie teste ich da, ob meine Lese-Funktion das zurückgibt, was ich erwarte? Genau das selbe Layer oder Feature erzeugen? Ist der Testcase dann nicht sehr kompliziert und unter Umständen auch sehr umfangreich? Und geht das überhaupt bei so C-API Anbindungen (SWIG)? Oder bei Datenbank-Rows? Ist row_gelesen == row_erwartet, wenn der Inhalt der Felder gleich ist? Ich weiss, die Fragen sind zum Teil recht naiv, aber das sind so konzeptionelle Fragen, die sich mir stellen ...

Dann habe ich noch eine Frage zur Dokumentationserzeugung. Sphinx gefällt mir eigentlich sehr gut, nur gibt es auch etwas, was eine API-Dokumentation automatisiert erzeugen kann? So etwas wie bei OpenLayers z.B.? Die verwenden ja Naturaldocs, was auch Python kann, aber gibt es da noch etwas anderes? Denn etwas unschön finde ich dabei, dass man im Code die Dokumentation schreibt.

Mir fehlt da leider der Überblick, deshalb wäre ich froh, wenn ich hier ein paar Anregungen bekommen könnte, was andere so verwenden

Danke

Frank
BlackJack

@frabron: `unittest` ist den *Unit-Bibliotheken nachempfunden. Ist ganz nett wenn man zum Beispiel schon JUnit von Java her kennt, aber für Python-Verhältnisse ein wenig "aufgeblasen". Darum gibt es Bibliotheken wie `nose` oder `py.test`, die beide auch mit `unittest`-Testfällen klar kommen, aber selber noch mal eine etwas schlankere API anbieten. Und auch das erfassen und ausführen von Testfällen schön einfach machen. Man kann die Kommandozeilenwerkzeuge von den beiden zum Beispiel einfach auf Module oder Verzeichnisse loslassen und die suchen dann selbstständig nach Modulen, Funktionen, und Klassen, deren Name einem Muster entspricht und führen diese als Tests aus.

Wenn Du nur Deinen Code testen willst, der aber externe Objekte oder teure "setups" benötigt, kannst Du "mock"-Objekte verwenden. Dafür gibt es dann auch wieder zig Implementierungen. Zum Beispiel `dingus` oder `mock`.

Schau mal in der Sphinx-Dokumentation nach den mitgelieferten Erweiterungen bei `autodoc` & Co.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

frabron hat geschrieben:Wie testet man, ob z.B. die XML-Repräsentation eines OSM-Datensatzes dem entspricht, was ausgelesen werden sollte? Mittels Zeichenketten-Vergleich? Da kann es doch eigentlich nicht auf Leerzeichen und Umbrüche ankommen, sondern nur das Markup sollte getestet werden ...
Naja, indem du halt normalisierst und dir irgendwelche Äquivalenzen ausdenkst. Ist so ähnlich wie die Frage: Wie vergleiche ich zwei Float-Zahlen auf Gleichheit. Die Lösung ist dann ein kleines Epsilon zu definieren um dass sich die zwei Zahlen höchstens unterscheiden dürfen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
deets

Um die Antwort von Leonidas zu konkretisieren - dass hier habe ich mal gebaut. Es definiert *eine* Form der Aequivalenz (wie Leonidas schon richtig sagt). Es gibt nicht die "eine", sondern je nach Problem auch andere Implementierungen.

Unter Umstaenden ist auch eine Reihe von xpath-Ausdruecken sinnvoll, um bestimmte Strukturelle Relationen robust zu erfassen. Meiner Erfahrung nach ist da das Modul lxml einfach unschaetzbar wertvoll.

Code: Alles auswählen

from lxml.etree import (
    fromstring,
    )

from genshi.core import Stream
from itertools import izip

def xml_equivalence(left, right):
    """
    Compares two given xml-documents (as strings)
    with tolerance regarding whitespace and attribute-order.
    """


    def text_equivalence(left, right):
        if left is None:
            left = ""
        if right is None:
            right = ""
        left = "".join(left.split())
        right = "".join(right.split())
        return left == right

    if isinstance(left, Stream):
        left = left.render()

    if isinstance(right, Stream):
        right = right.render()

    if isinstance(left, basestring):
        left_doc = fromstring(left)
    else:
        left_doc = left
    if isinstance(right, basestring):
        right_doc = fromstring(right)
    else:
        right_doc = right

    def compare(left, right):
        if left.tag != right.tag:
            return False

        if not text_equivalence(left.text, right.text):
            return False
        if not text_equivalence(left.tail, right.tail):
            return False

        if left.attrib != right.attrib:
            return False

        if len(left) != len(right):
            return False

        for left_child, right_child in izip(left.iterchildren(), right.iterchildren()):
            if not compare(left_child, right_child):
                return False
        return True


    return compare(left_doc, right_doc)
Die Stream-Geschichte bezieht sich auf Genshi, und kann natuerlich einfach rausgeschmissen werden, aber schaden zu zeigen, dass auch das geht, kann es ja nicht ;)
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Vielen Dank für die Antworten soweit. Als Testengine schaue ich mir dann mal nose an, da liegt mir ja auch mit den SQLAlchemy Tests hinreichend Anschauungsmaterial vor. Leonidas Antwort hab ich erst mal nicht richtig verstanden (Epsilon?! Was ist das nun wieder :D), aber das hat deets ja zum Glück noch mal am Beispiel erläutert. Einen XML-Parser als Werkzeug beim Vergleichen zu verwenden hatte ich auch schon im Kopf gehabt, wusste aber nicht, wie man den Vergleich durchführen sollte. Dank deets Schnipsel sollte das ja nun zu Schaffen sein.

Wenn ich die Sphinx Dokumentation zu Autodoc lese, spricht also nix dagegen, Teile der Dokumentation in den Quelltext zu verlagern? Ich dachte, immer, das wäre kein guter Stil, da ja zwei Sachen vermischt werden. So wie bei HTML Layout und Inhalt nicht vermischt werden sollten halt ...
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

frabron hat geschrieben:Leonidas Antwort hab ich erst mal nicht richtig verstanden (Epsilon?! Was ist das nun wieder :D)
Oh sorry, Epsilon ist einfach eine kleine Zahl. War aber eh nur als Beispiel gedacht.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Kein Problem, hat sich dann aus dem Kontext und Wikipedia ergeben. Das war nur der erste Moment: Epsilon? wtf? :lol:
Antworten