Unit Tests mit Python

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
gecko
User
Beiträge: 47
Registriert: Samstag 9. Juni 2007, 10:48

Über das Thema existiert kein richtiger Thread.

Das Standardmodul ab Python >2.0
http://docs.python.org/lib/module-unittest.html

Für Python 2.0 User:
http://pyunit.sourceforge.net/

Ein schönes Beispiel:
http://www.diveintopython.org/unit_testing/index.html

Wäre schön wenn sich ein paar Erfahrungsberichte sammeln würden zum Umgang mit Unittests.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

gecko hat geschrieben:Über das Thema existiert kein richtiger Thread.
Na dann :roll:

LG
gecko
User
Beiträge: 47
Registriert: Samstag 9. Juni 2007, 10:48

Unit-Tests != TDD
BlackJack

Aber Unit-Tests sind ein integraler Bestandteil von TDD.
gecko
User
Beiträge: 47
Registriert: Samstag 9. Juni 2007, 10:48

Okay, hier mal ein Beispiel für den Unittest einer Funktion. Der Test ist absichtlich nicht vollständig. Den Code selber kann man sich auch noch optimieren.


Die Funktion nimmt eine Liste und prüft ob die Elemente benachbart sind. Die Nachbarfunktion ist in dem Fall einfach die Distanz x-y == 1. D.h. 2 und 3 sind benachbar, aber nicht 2 und 4 etc.

Die Liste ist dabei ein Ring, das heißt dass der Indexnachbar des letzten Elements das erste Element ist (=wrap around).

Code: Alles auswählen

# -*- coding: iso-8859-1 -*-
# std_utils.py

def listGap(li, n):
    """ check for (n) gaps in list """
    if n > len(li): return False
    c = 0 # count number of neighbours
    for el in li: 
	    for el2 in li:
		    i = li.index(el) # index
		    j = li.index(el2)

		    next = False
		    if  math.fabs(i-j) == 1: 
			    next = True
		    
		    # round the corner elements are considered 
		    # [X,.,.,.,.Y] ==> compare X and Y aswell
                    l = len(li)
		    if i == l-1 and j == 0 :
			    next = True
		    if j == l-1 and i == 0 :
			    next = True

		    k = li[i] # values
		    l = li[j]
		    			    
		    if next:
			    if math.fabs(k-l) == 1:
				    c+=1 # found one			    
			    
    return c >= n

Code: Alles auswählen

# -*- coding: iso-8859-1 -*- 
#unittest_std_utils.py

import unittest
from std_utils import *

class StdUtilTest(unittest.TestCase):
    """ Unit test for std_utils """
	
    def testlistGap(self):
	    """ check List Gap conversion of a list with 2 elements """
	    assert listGap([0,1],1)
	    assert listGap([1,0],1)
	    assert not listGap([0,1],0)
	    assert not listGap([1,0],0)
	    assert not listGap([1,1],1)
	    assert not listGap([2,2],1)
	    assert listGap([3,3],0)
	    assert listGap([4,4],0)
	    
    def testlistGap(self):
	    """ check List Gap conversion 5 in the row """
	    li = [1,2,3,4,5]
	    
	    assert listGap(li,1)
	    assert listGap(li,2)
	    assert listGap(li,3)
	    assert listGap(li,4)
	    assert listGap(li,5)
	    assert not listGap(li,6)
	    assert not listGap(li,7)

    def testlistGap(self):
	    """ check List Gap conversion : round the corner"""	    
	    li = [1,2,3,4,0]
	    
	    assert listGap(li,1)
	    assert listGap(li,2)
	    assert listGap(li,3)
	    assert listGap(li,4)
	    assert listGap(li,5)
	    assert not listGap(li,6)
	    assert not listGap(li,7)
	    
    def testlistGap(self):
	    """ check List Gap conversion 3 in the row """	    
	    li = [1,4,5,6,0]
	    
	    assert listGap(li,1)
	    assert listGap(li,2)
	    assert listGap(li,3)
	    assert not listGap(li,4)
	    assert not listGap(li,5)
	    assert not listGap(li,6)
	    assert not listGap(li,7)
	    
    def testlistGap(self):
	    """ check List Gap conversion 2 in the row """	    
	    li = [1,0,43,44,55]
	    
	    assert listGap(li,1)
	    assert not listGap(li,2) # <-- AssertionError!
	    assert not listGap(li,3)
	    assert not listGap(li,4)
	    assert not listGap(li,5)
	    assert not listGap(li,6)
	    assert not listGap(li,7)

    def testDummy(self):
	    assert True
	    assert False
	    
if __name__ == "__main__":
    unittest.main() 

Was mir allgemein auffällt ist, dass die Assertions nach dem AssertionError nicht mehr ausgeführt werden. D.h. man sollte den Testcase auch wirklich so organisieren, dass die einzelnen Cases unabhängig sind und zwei Fails in einem Case keinen Informationsgewinn über einem Fail sind.
poker
User
Beiträge: 146
Registriert: Donnerstag 20. September 2007, 21:44

gecko hat geschrieben:Unit-Tests != TDD
Jain, wie BlackJack schon sagte ist es Zentraler Bestandteil von TDD.

Aber davon abgesehen dreht sich der von CrackPod verlinkte Thread nicht explizit um TDD, wie man an meinen Ausführungen sehen kann.

TDD im eigentlichen Sinne ist ja erst die Tests zu schreiben und **dann** die Funktionalität zu implementieren die der Test beschreibt ;) Man könnte diese Art der Verwendung auch mit "Spezifikation schreiben" gleichsetzen, wo die Spezifikation für die zu implementierende Funktion/Classe von sich aus eine Validierung vornimmt, wenn man sie startet![1]

Darum geht es aber zum größten Teil nicht in dem Thread. Sondern es geht um die Vorteile von Unit tests und die explizite zunahmen von Code Andeckungstestst (=Coverage) um sich das Leben zu erleichtern(!). Besonders ist der Fokus des Threads auf Coverage gelegt, da sehr wenige sowas nutzen, was sehr schade ist, da es die einzige und "sicherste" Möglichkeit darstellt um eine 100% Abdeckung seines Codes zu erreichen.

OT:
[1]: Die Betrachtungsweise klingt im ersten Augenblick ein wenigeigenartig aber: Was ist letztendlich eine Spezifikation? Eine **ganz genaue** formalisierte Beschreibung eines zum implementierenden Systems. Ob die Spezifikation in natürliche Sprache oder in einem Programmcode (=Untit test) + Kommentaren/Docstrings Ausgedürckt wird, ist letztendlich IMO irrelevant.
Man kann durchaus sich "Utils" und eigene Syntax implementieren, die aus der auf Programmcode basierenden Spezifikation auch eine Komprimierte Form in natürlicher Sprache überführen kann. -- Dafür muss man natürlich die Toolkits ein wenig aufbohren.


----
gecko hat geschrieben:Über das Thema existiert kein richtiger Thread.

Das Standardmodul ab Python >2.0
http://docs.python.org/lib/module-unittest.html

Für Python 2.0 User:
http://pyunit.sourceforge.net/

Ein schönes Beispiel:
http://www.diveintopython.org/unit_testing/index.html

Wäre schön wenn sich ein paar Erfahrungsberichte sammeln würden zum Umgang mit Unittests.
Wie im anderen Thread erwähnt und auch dem Konsens der meisten Python-Entwicklern entspricht, sollte man das mit Python mitgelieferte ``unittest`` nicht mehr nutzen, weil es mittlerweile viel Bessere und "Pythonischere" (Ja, Leonidas nun habe ich es getan und den Begriff Pythonisch auch benutzt :D) Lösungen gibt.

Gute Lösungen sind py-test und auch nose.

IMO sollte bei dem Thema Unit test auch das Thema Code Abdeckung (=Coverage) nicht fehlen und daher sollte man auch solche hier Aufführen: coverage von Ned Batchelder oder das auf Ned Batchelder basierende coverage figleaf von Titus Brown...


...wo wir auch bei deinem Unit test example angelangt sind. Die zu testende Funktion `listGap` ist schon etwas Komplizierter: Der Unit test liefert mir keine 100% Gewissheit ob wirklich alle Fälle (Die vielen Verzweigungen) abgedeckt werden. Dabei ist für mich gar nicht mal entscheidend das wirklich alle Fälle vom Unit test abgedeckt werden sollen/müssen, nein für mich ist hier das entscheidende (Bei dieser Verzweigungsreichen Funktion) zu wissen, ob da nicht toter Code rumliegt den man rausschmeißen könnte.

Ich geh mal ein wenig weiter und konstruiere mal ein Beispiel:
Angenommen deine Funktion ist vom Unit test nicht 100%ig abgedeckt, aber alle in der Funktion befindlichen Verzweigungen sind momentan erforderlich. Was passiert nun wenn du deine Funktion abänderst (Zum beispiel effizienter machst, etc.) und dadurch die nicht vom Unit test abgedeckten bereiche überflüssig werden (Sprich werden nie aufgerufen)? In dem Fall haben wir es mit unerwünschten Toten Code zu tun, toter Code den du in a**n anderen Projekten betrachten kannst, mangels Coverage und sauberen Unit tests. Klar, wenn du den Totalen Überblick hast, wirst du bei Änderung selber den Überflüssigen Code identifizieren und löschen. Aber welche Gewissheit hast du dafür, das du nicht doch was vergisst? IMO keine, da selbst Profis davor nicht gewappnet sind ;) Daher pädiere ich für eine gewisse "Sicherheit" die nur durch einen Abdeckungstest erreicht werden kann.

Wie gesagt, es geht mir Primär garnicht darum das deine Funktion 100% abgedeckt sein muss (Auch wenn es wünschenswerte ist und IMO nur solcher Code als potenziell "Fehlerfrei" zu betrachten ist.), sondern dass als Minimum ein Abdeckungstest hinter deinem Unit test gekoppelt wird. Es macht das schreiben von Tests halt einfacher und fehlerfreier, als wenn man das alles aus dem Stegreif macht...Und dann kann man immer noch selber entscheiden ob und in wiefern Code 100% abgedeckt werden soll.
Benutzeravatar
lutz.horn
User
Beiträge: 205
Registriert: Dienstag 8. November 2005, 12:57
Wohnort: Pforzheim

Der Vorteil von TDD ist, dass man immer eine Testabdeckung von 100% erreicht. Da nur der Code geschrieben wird, der zum erfolgreichen Ausführen des zuletzt geschriebenen Tests benötigt wird, kommt man nie in die Versuchung, nicht abgedeckten Produktivcode zu schreiben. Beim Schreiben der Tests ergibt sich die Spezifikation des zu testenden Codes nebenbei. Falls der Code dann nicht all das kann, was man sich vielleicht gewünscht hat, so hat man einfach zu wenig Tests geschrieben.
gecko
User
Beiträge: 47
Registriert: Samstag 9. Juni 2007, 10:48

poker hat geschrieben:
gecko hat geschrieben:Unit-Tests != TDD
Jain, wie BlackJack schon sagte ist es Zentraler Bestandteil von TDD.

Aber davon abgesehen dreht sich der von CrackPod verlinkte Thread nicht explizit um TDD, wie man an meinen Ausführungen sehen kann.
Danke zunächst für das ausführliche Kommentar und die Hinweise zu den Packages.

Also die Idee des Unit-Tests ist schon eine andere als das Test-Driven-Development. Man kann sehr gut Unit-Tests machen ohne TDD zu betreiben.
Man kann dazu einfach das Extreme-Programming in die Runde schmeißen und schon ist die Verwirrung perfekt. XP setzt TDD voraus, TDD UT, aber UT setzt weder XP noch TDD voraus.
poker hat geschrieben: Ob die Spezifikation in natürliche Sprache oder in einem Programmcode (=Untit test) + Kommentaren/Docstrings Ausgedürckt wird, ist letztendlich IMO irrelevant.
Da muss ich widersprechen. Die ganze Idee des TDD ist doch, dass der Test die perfekte Dokumentation ist, weil 1. die Anforderungen genau spezifiziert werden müssen und ganz wichtig
2. die Anforderungen automatisch dem Code entgegengestellt werden.
poker hat geschrieben: nein für mich ist hier das entscheidende (Bei dieser Verzweigungsreichen Funktion) zu wissen, ob da nicht toter Code rumliegt den man rausschmeißen könnte.
Ich glaube man kann das begrifflich unterscheiden zwischen verschiedenen Test-Modalitäten: Funktionstest, Lasttest, Performancetest, Regressionstest, ...
poker hat geschrieben: Angenommen deine Funktion ist vom Unit test nicht 100%ig abgedeckt, aber alle in der Funktion befindlichen Verzweigungen sind momentan erforderlich.
Was passiert nun wenn du deine Funktion abänderst (Zum beispiel effizienter machst, etc.) und dadurch die nicht vom Unit test abgedeckten bereiche überflüssig werden
(Sprich werden nie aufgerufen)?
Das ist in diesem Fall auch genau was passiert ist. Anforderungen ändern sich, als auch das Wissen wie man den Code effizienter umsetzen kann.
Das entscheidende ist jedoch der Arbeitsprozess. Aktuell bin ich relativ ineffektiv was das Inspecten von Code zur Laufzeit angeht. Eine dort zufriedenstellende (kostenlose) IDE habe ich bisher noch nicht gesehen.

Hier der aktuelle Code. Die Funktion liefert jetzt die Liste mit der längsten Verbindung zurück, anstatt nur die Zahl der Verbindungen.

Code: Alles auswählen

#std_utils.py

def listGap(li):
    """ return longest linked up list
    check also for a link between the last and the first member
    (could be extend for full wrap-around)"""
    clist = [] # metalist
    roli = [] # list of neighboured elements
	    
    for i in range(len(li)-1): 
	    j = i+1
	    neighbour = (li[j]-li[i] == 1)
	    last = j == len(li)-1

	    if neighbour:
		    if li[i] not in roli: roli.append(li[i])
		    if li[j] not in roli: roli.append(li[j])
		    #print 'roli', roli
		    
	    if (neighbour and last) or not neighbour: # 
		    #print 'last'
		    if len(roli) == 0:
		    	roli.append(li[j]) # append last element
		    	
		    clist.append(roli)
		    roli = []
		    
    #print 'clist: ', clist 	    
    maxl = maxList(clist)
    return maxl
    
def listGapHead(li):
    meta = []
    if li[-1]-li[0] == 1:
	    wrap = listGap([li[-1]] + li[1:])
	    meta.append(wrap)
    else:
	    nowrap = listGap(li)
	    meta.append(nowrap)
    return maxList(meta)
    
def maxList(metalist):
    """ return the list with the maximum elements in a list of lists """
    rel = []
    for l in metalist:
	    if len(l) > len(rel):
		    rel = l
    return rel

Code: Alles auswählen

#unittest_std_utils.py
# -*- coding: iso-8859-1 -*- 

import unittest
from std_utils import *

class StdUtilTest(unittest.TestCase):
    """ Unit test for std_utils """
    
    def testlistGap(self):
        """ gap list : emtpy list """

    	a = []
	assert listGapHead(a) == a

    def testlistGap(self):
        """ gap list : one element """

    	a = [1]
	assert listGapHead(a) == a

    def testlistGap(self):
        """ gap list : two elements """

    	a = [1,4]
	assert len(listGapHead(a)) == 1

    def testlistGap(self):
        """ gap list : three elements, row 2 """

    	a = [1,4,0]
	assert len(listGapHead(a)) == [1,0]

    def testlistGap(self):
        """ gap list : three elements, row 3"""

    	a = [1,2,0]
	assert len(listGapHead(a)) == a

    def testlistGap(self):
        """ gap list : five elements, row 2 """

    	a = [1,3,4,8,9]
	assert len(listGapHead(a)) == 2

    def testlistGap(self):
        """ gap list : five elements, row 2 """

    	a = [8,7,6,5,9]
	assert len(listGapHead(a)) == 2

    def testlistGap(self):
        """ gap list : five elements, row 5 """

    	a = [8,9,10,11,12]
	#print 'a: ', listGapHead(a)
	assert len(listGapHead(a)) == 5
	
    def testmaxlist1(self):
        """ test maximum list function : one element """
	assert maxList([[]]) == []
    	a = []
	b = [1]
	meta = []
	meta.append(a)
	meta.append(b)
    	assert maxList(meta) == b
	
    def testmaxlist2(self):
        """ test maximum list function: two elements"""
    	a = [1]
	b = [3,4]
	meta = []
	meta.append(a)
	meta.append(b)
    	assert maxList(meta) == b

    def testmaxlist3(self):
        """ test maximum list function: 3 elements"""
    	a = [1]
	b = [3,4]
	c = [7,7,7,7]
	meta = []
	meta.append(a)
	meta.append(b)
	meta.append(c)
	
    	assert maxList(meta) == c	
	
if __name__ == "__main__":
    unittest.main() 
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Mir misfällt irgendwie, dass du massenweise Methoden gleichen Namens überschreibst.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

gecko hat geschrieben:Man kann dazu einfach das Extreme-Programming in die Runde schmeißen und schon ist die Verwirrung perfekt. XP setzt TDD voraus, TDD UT, aber UT setzt weder XP noch TDD voraus.
Ist aber kein Problem, wer nicht auf die richtige Reihenfolge kommt, der kann es einen Computer berechnen lassen, mit topologischer Sortierung ist das ganz simpel ;)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
gecko
User
Beiträge: 47
Registriert: Samstag 9. Juni 2007, 10:48

Y0Gi hat geschrieben:Mir misfällt irgendwie, dass du massenweise Methoden gleichen Namens überschreibst.
Richtig. Folgende Konvention habe ich mir überlegt:

Code: Alles auswählen

# -*- coding: iso-8859-1 -*- 
import unittest

class MyUnit(unittest.TestCase):
    """ MyUnitTest description """
	    
    def testCase_a_1(self):
	    """ my test case 1: info about data """
	    
	    # ////// SETUP TEST DATA /////////
	    a = 1
	    # ////// TEST DATA /////////
	    assert True
	    self.assertEqual(a,1)

    def testCase_a_2(self):
	    """ my test case 2 similar to 1: a =2"""
	    
	    # ////// SETUP TEST DATA /////////
	    a = 2
	    # ////// TEST DATA /////////
	    assert True
	    self.assertEqual(a,2)

    def testCase_b_2(self):
	    """ my test case: b =2 """
	    
	    # ////// SETUP TEST DATA /////////
	    b = 2
	    # ////// TEST DATA /////////
	    assert True
	    self.assertEqual(b,2)
	    
if __name__ == "__main__":
    unittest.main() 
Werde mal einen besseren Stub online stellen wenn ich mir o.g. Frameworks angeschaut habe. Wenn jemand schon so etwas hat und vorstellen will umso besser.
Antworten