Unittests in Python Authors: Date: Version: Berthold Höllmann 2009-09-06 13:33:01 +0200 (So, 06 Sep 2009) 23 Inhalt 1 Einleitung 2 2 Talk Format 2 3 Linus’ Law 2 4 Warum also testen? 2 5 Testmethoden 2 6 xUnit Paradigma 3 7 unittest 3 8 unittest: Einfaches Beispiel aus der Python Dokumentation 3 9 unittest: Mehr Details 3 10 unittest: Mehr Details (2) 3 11 Mehr zu ”unittest“ 4 12 Kritik an unittest 4 13 Andere Optionen 4 14 doctest 4 15 doctest Beispiel des vorhergehenden Tests 4 16 Noch ein Beispiel 5 17 doctest Details 5 18 doctest Details (2) 5 19 doctest Details (3) 5 20 doctest Details (4) 6 1 21 doctest Details (5) 6 22 doctest Details (6) 6 23 doctest details (7) 6 24 doctest details (8) 7 25 doctest Nutzen 7 26 Ausführbare Dokumentation 7 27 Kritik an doctest 7 28 Unklare Bereiche 8 29 Testing Links 8 30 Beispiel 8 31 Diskussion 8 1 Einleitung Testen essentieller Bestandteil des Software Entwicklungsprozess. 2 Talk Format • Einführung in das Testen mit Python • Beispiel eines Test Problems • Text nach Intro to python testing talk (unittest, doctest, coverage), Original Quellen sind dort verlinkt. 3 Linus’ Law given enough eyeballs, all bugs are shallow Code Complete“ führt aus, dass umfangreiche Beta Tests die meiste Fehler (˜75%) aufdecken, durch ” testen dagegen werden etwa 30-40% der Fehler entdeckt. 4 Warum also testen? • Vertrauen • Refaktorieren • Zur Zusammenarbeit mit anderen Entwicklern 5 Testmethoden Alle Arten, hier werden wir haupsächlich «white box» Unit Tests behandeln. Ein Unit Test testet in der Regel genau einen Aspekt. 2 6 xUnit Paradigma Abgeleitet von Kent Beck’s SUnit # psuedocode setup() # verschiede Annahmen bezüglich des Verhaltens teardown() 7 unittest Ursprünglich als PyUnit entwickelt, modelliert nach JUnit (das sich an SUnit anlehnt). Test Klassen werden von unittest.Testcase abgeleitet. Jede Methode deren Name mit test anfängt ist ein Testcase. 8 unittest: Einfaches Beispiel aus der Python Dokumentation import random import unittest class TestSequenceFunctions(unittest.TestCase): def setUp(self): self.seq = range(10) def testchoice(self): # note that method begins with "test" element = random.choice(self.seq) self.assert_(element in self.seq) if __name__ == ’__main__’: unittest.main() 9 unittest: Mehr Details setUp() und tearDown() werden vor bzw. nach jedem Testcase ausfeführt. setUp stellt die notwendige Umgebung für die Testfälle sicher (z.B. füllen einer Datenbank, o.Ä.). tearDown räumt wieder auf, um den Zustand vor den Tests wieder herzustellen. 10 unittest: Mehr Details (2) Es unittest.TestCase hat verschiedene assert* und fail* Methoden: • assert_(expr [,msg]) - Wenn expr unwahr ist, signalisiere den Misserfolg mit msg. Korrespondiert zu failUnless. • assertEqual(first, second [, msg]) • assertNotEqual • assertRaises(exception, callable, ...) • ... 3 11 Mehr zu ”unittest“ Siehe auch: • TestSuite: Ähnlich TestCase, definieret aber keine Tests, sondern Grupiert sie. • TestResult: Hält die Testergebnisse der TestCase und TestSuite Instanzen. • TestLoader: Generiert eine Folge von Tests aus Klassen und Modulen. 12 Kritik an unittest Negativ: • Warum sind die Klassen nach Java Klassen entworfen? (Python untypische Vererbung und Abstraktion) • Zu schwergewichtig, zu umfangreiche Klassen, frameworky • Der Fokus liegt auf dem Testen, dies erschwert das aktualisieren -- warum schlägt ein Test fehl? Positiv: • Ist Teil der Standard Bibliothek • Unkompliziert 13 Andere Optionen • Nose • Twisted Trial • py.test 14 doctest Verwandelt Python Docstrings (in Modulen, Klassen, Funktionen oder Methoden) in Tests. Ursprüngliche Motivation war es die Korrektheit von Dokumentation sicherzustellen. Nimmt Ausschnitte aus interaktiven Sitzungen und lässt sie ablaufen. 15 doctest Beispiel des vorhergehenden Tests """ >>> import random >>> seq = range(10) >>> element = random.choice(seq) >>> element in seq True """ import doctest doctest.testmod() 4 16 Noch ein Beispiel import doctest def factorial(n): """Return the factorial of n, an exact integer >= 0. >>> [factorial(n) for n in range(6)] # doctest: +SKIP [1, 1, 2, 6, 24, 120] >>> factorial(-1) # doctest: +SKIP Traceback (most recent call last): ... ValueError: n must be >= 0 """ # actual implementation removed.... doctest.testmod() 17 doctest Details Es folgen einige Details über das doctest Here is some details about the doctest module. Taken from the doctest docs. >>> # comments are ignored >>> foo = "bar" >>> foo #implicit print ’bar’ >>> if foo == "baz": ... print "baz" ... else: ... print foo ... bar 18 doctest Details (2) Wahrnung. Kann Leerzeichen nicht direkt verarbeiten, stattdessen muss <BLANKLINE> benutzt werden. >>> print "" <BLANKLINE> 19 doctest Details (3) Backslashes. Raw Docstrings benutzen (dieser ist nicht raw) oder sie müssen geschützt werden. >>> line = "foo\\n" >>> print line foo <BLANKLINE> # will throw a value error here if not escaped 5 20 doctest Details (4) Anfangsspalte ist irrelevant >>> 2 + 2 4 Dies ist weiter eingerückt, aber das spielt keine Rolle >>> 2 + 2 4 21 doctest Details (5) Exceptions. Die erwartete Ausgabe muss mit dem Traceback Header starten.: >>> [1, 2, 3].remove(42) Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: list.remove(x): x not in list Der Traceback Stack kann aus durch einen Platzhalter (...) ersetzt werden.: >>> [1, 2, 3].remove(42) Traceback (most recent call last): ... ValueError: list.remove(x): x not in list 22 doctest Details (6) Verschiedene Optionen und Anweisungen. Man beachte die #doctest: >>> print range(20) #doctest: +NORMALIZE_WHITESPACE [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] >>> print range(20) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE [0, 1, ..., 18, 19] >>> (1, 2)[3] = ’moo’ #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: object doesn’t support item assignment 23 doctest details (7) Umgang mit dict s: >>> foo = dict(a=1, b="hello", c="bye") >>> foo # Abhängig von der Python version schlägt dies fehl {’a’: 1, ’c’: ’bye’, ’b’: ’hello’} Ein Workaround ist es die Schlüssel zu sortieren: 6 >>> items = foo.items() >>> items.sort() >>> items [(’a’, 1), (’b’, ’hello’), (’c’, ’bye’)] Ich vergleiche die dict s lieber: >>> foo == {’a’: 1, ’c’: ’bye’, ’b’: ’hello’} True 24 doctest details (8) Behandlung von Adressen: >>> class C: pass >>> c = C() >>> c # will most likely fail #doctest: +SKIP <__builtin__.C instance at 0xb7a210ec> >>> c #doctest: +ELLIPSIS <__builtin__.C instance at 0x...> 25 doctest Nutzen Drei eindeutige Anwendungsfälle: • Testen von Beispielen in Docstrings • Regressionstest • Ausführbare Dokumentation Dies sind in Erscheinung und Nutzen sehr unterschiedliche Anwendungen. 26 Ausführbare Dokumentation Schema Defined Buttons ---------------------Let’s now create a schema that describes ... >>> import zope.interface #doctest: +SKIP >>> class IButtons(zope.interface.Interface): ... apply = button.Button(title=u’Apply’) ... cancel = button.Button(title=u’Cancel’) #doctest: +SKIP Gefunden bei zope (Die Notiz gehauptet 100% Abdeckung) 27 Kritik an doctest Negativ • Kann unübersichtlich werden (z.B. Workaround for Leerzeilen) • Der Setup/Teardown Code kollidiert z.T. mit der Dokumentation. 7 Positiv • Leichtgewichtig • Einfach, keine API • In Python enthalten • Ausgerichtet auf Dokumentation 28 Unklare Bereiche Wie erkenne ich die Effektivität meiner Tests? 29 Testing Links • Python Testing Tools Taxonomy (http://pycheesecake.org) • TIP - Testing in Python mailing list 30 Beispiel Für ein Spalten orientiertes Ausgabeformat soll eine Funktion geschrieben werden, die auf eine Gegebene Anzahl von Stellen Zeichenketten, Integer und Float ausgeben soll. Bei Float soll maximale Genauigkeit erreicht werden. 31 Diskussion ??? Hamburger Python User Group • 2008/10/06 View document source. Generated on: 2009-09-28 13:15 UTC. Generated by Docutils from reStructuredText source. 8