verteilte anwendungen teil p2: objektorientierung und...
TRANSCRIPT
21.04.20 1Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Verteilte Anwendungen
Teil P2: Objektorientierung und Ergänzungen
2Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Überblick
� Klassen
� Scope
� Type Hints
� Dokumentation
� Datei I/O
� Exceptions
� Unit-Tests
� Module und Packages
3Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Klassen I
� Der Konstruktor hat den festen Namen __init__(self).
� Attribute brauchen nicht deklariert zu werden.
� Der erste schreibende Zugriff richtet Klassen-lokale Variablen ein.
� Der 1. Parameter der Methoden ist immer self; dessen Wert wird intern gesetzt.
� Zugriff auf Attribute im Inneren der Klasse erfolgt immer über self: self.name.
class ClassName(): ... Attribute ... def __init__(self, parameter1, parameter2, ...): Block def name(self, parameter1, parameter2, ...): Block def name(self, parameter1, parameter2, ...): Block
4Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Bemerkungen
� self kann als ein Zeiger auf die interne Datenstruktur des Objektes angesehen werden.
� Warum dieser (interne) Zeiger nicht wie in Java und PHP per Compiler den Methoden mitgeteilt wird, ist unklar.
� Dieser self-Parameter wird beim Aufruf nicht angegeben.
� Zur Erzeugung des Objektes wird der Konstruktor mit dem Namen der Klasse ohne new aufgerufen.
5Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Klassen II - Beispiel
class Rectangle(): def __init__(self,width,height): self.wdth= width self.hght= height def area(self): return self.wdth*self.hght def perimeter(self): return 2*(self.wdth+self.hght)
rec1= Rectangle(3,7)rec2= Rectangle(42,42)for r in [rec1, rec2]: print("Breite %4d Höhe %3d"%(r.wdth,r.hght)) print("Fläche %4d Umfang %3d"%(r.area(),r.perimeter()))
Breite 3 Höhe 7Fläche 21 Umfang 20Breite 42 Höhe 42Fläche 1764 Umfang 168
Output
6Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Magische Methoden I
� Die Methode __str__ entspricht der toString()-Methode in Java.
� Der Unterschied zwischen __str__ und __repr__ besteht darin, dass letztere Methode immer das Objekt eindeutig machen muss, auch wenn es mehrere Bob Müllers gibt...
class Person(): def __init__(self,firstname: str,surname:str)-> None: self.firstname= firstname self.surname= surname def __str__(self): return f"{self.firstname}" def __repr__(self): return f"{self.firstname} {self.surname}"bm= Person("Bob","Müller")print(bm)print(f"{bm}")print(f"{bm!r}")
BobBobBob Müller
Output
7Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Magische Methoden II
� __del__(self) ist der Destruktor, der beim Vernichten (nicht Freigeben) des Objekts aufgerufen wird.
� __delete__(self, instance) wird aufgerufen, wenn mit dem del-Kommando ein Attribut innerhalb eines bestehenden Objekts gelöscht wird.
� Siehe
– https://rszalski.github.io/magicmethods/
– https://www.geeksforgeeks.org/python-__delete__-vs-__del__
Diese Methoden werden auch Dander-Methoden genannt:Double Under (Underscores)
Es gibt viele magische Methoden, hier zwei weitere:
8Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Sichtbarkeit von Attributen I
� id ist wie public
� _id ist wie protected
� __id ist wie private
Mit id als Attribut, Variable oder Funktion/Methode
� Die Eigenschaften von private, protected und public sind so wie in Java.
� Diese Schlüsselworte gibt es nicht in Python.� Das Ganze ist eine Konvention, an die sich gehalten werden
kann.� Die dritte Form dadurch privatisiert, dass dem Namen __id
intern ein anderer Name (zusammen mit dem Klassennamen) gegeben wird.
9Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Sichtbarkeit von Attributen II
� Die Routine dir() liefert eine Liste aller definierten Symbole des Parameters – hier die Klasse Scope.
class scope(): att : int = 1 _att : int = 2 __att : int = 3
print(dir(scope))s= scope()s.att = 4s._att = 5s._scope__att = 6
['__annotations__', ... '_att', '_scope__att', 'att']
Output
10Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Regeln zur Sichtbarkeit (Scope) I
� Local: innerhalb des Blocks oder Funktion, aber nicht als global erklärt
� Enclosing-function: Lokal innerhalb der Funktion bzw. Klasse, die die betroffene Funktion umschließen
� Global: global auf der höchsten Ebene (Modul, Package)
� Built-in (Python): In der Sprache bzw. Bibliotheken definiert
Regel LEGB
Also ganz normal, von Innen nach Außen gehend geht derSuchprozess bei der Bestimmung, welche Variable gemeint ist.
Alternative: Jede Variable hat einen eigenen Namen –dann ist immer klar, welche gemeint ist
11Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Regeln zur Sichtbarkeit (Scope) II
� Lokale Zuweisungen an globale Variablen verhalten sich genauso wie in PHP und Java.
� Globale Variablen können lokal benutzt werden.
a = 1def fn(): a = 2 print(a)fn()print(a)
22
Output
a = 1def fn(): #a = 2 print(a)fn()print(a)
11
Output
12Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Regeln zur Sichtbarkeit (Scope) III
� Die erste lokale Zuweisung richtet eine Variable lokal ein.
� Mit dem Schlüsselwort global kann auch lokal eine globale Variable eingerichtet und lokal benutzt werden.
def fn(): a = 2 print(a)fn()print(a)
NameError: name 'a' is not defined
Output
def fn(): global a a = 2 print(a)fn()print(a)
22
Output
13Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Regeln zur Sichtbarkeit (Scope) IV
� https://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html
� https://www.datacamp.com/community/tutorials/scope-of-variables-python
� https://people.cs.clemson.edu/~malloy/courses/pythonProg-2015/lessons/scope/paper.pdf
Siehe
14Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Vererbung I - Einfach
� Vererbung erfolgt über den Parameter bei der Klassendefinition.
� Es ist mehrfache Vererbung erlaubt.
class ClassName(UppClass1, ...): ... Attribute ... def __init__(self, parameter1, parameter2, ...): Block def name(self, parameter1, parameter2, ...): Block def name(self, parameter1, parameter2, ...): Block
15Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Vererbung II - super
� Das Aufrufen des Konstruktors erfolgt über super() oder wie links angegeben.
� Der Effekt ist derselbe.
class Parent(): def __init__(self): print('Parent.__init__()')class Child(Parent): def __init__(self): Parent.__init__(self) print('Child.__init__()')c= Child()
class Parent(): def __init__(self): print('Parent.__init__()')class Child(Parent): def __init__(self): super().__init__() print('Child.__init__()')c= Child()
Parent.__init__()Child.__init__()
Output
16Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Vererbung III - Mehrfach
� Wenn keine Methode überschrieben wird, d.h. wenn alle Namen eindeutig sind, ist die Welt in Ordnung.
(01) class A():(02) def a(self):(03) print('A.a()')(04) class B():(05) def b(self):(06) print('B.b()')(07) class C(A,B):(08) def c(self):(09) print('C.c()')(10) c= C(); c.a(); c.b(); c.c()
A.a()B.b()C.c()
Output
17Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Vererbung IV - Mehrfach
� Jetzt stehen zwei Methoden Namens a zur Verfügung.
� Es wird die Liste der Oberklassen in der Reihenfolge der Angabe durchsucht bis eine Methode mit dem richtigen Namen gefunden wird.
(01) class A():(02) def a(self):(03) print('A.a()')(04) class B():(05) def a(self):(06) print('B.a()')(07) class C(A,B):(08) def c(self):(09) print('C.c()')(10) c= C(); c.a(); c.c()
A.a()C.c()
Output
18Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Vererbung V - Mehrfach
� Wenn die konkurrierenden Methoden überschrieben werden, wird es wieder einfach.
(01) class A():(02) def a(self):(03) print('A.a()')(04) class B():(05) def a(self):(06) print('B.a()')(07) class C(A,B):(08) def a(self):(09) print('C.a()')(10) c= C(); c.a();
C.a()Output
19Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Was ist None?
� Das Keyword None dient zur Darstellung von Null (wie in PHP und Java).
� Es kann allen Variablen und Objekten zugewiesen werden.
� None hat den Wert None und ist vom Typ NoneType (analog zu PHP).
� None ist konsequenterweise ein Objekt.
20Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Type hints I
class Rectangle(): wdth : float hght : float def __init__(self,width: float,height: float): self.wdth= width self.hght= height def area(self) -> float: return self.wdth*self.hght def perimeter(self) -> float: return 2*(self.wdth+self.hght) for r in [Rectangle(3.0,7.0),Rectangle(42.0,42.0)]: print("Breite %4.2f Höhe %3.2f"%(r.wdth,r.hght)) print("Fläche %4.2f Umfang %3.2f"%(r.area(),r.perimeter()))
Breite 3.00 Höhe 7.00Fläche 21.00 Umfang 20.00Breite 42.00 Höhe 42.00Fläche 1764.00 Umfang 168.00
Output
21Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Type hints II
� Syntax:
– Parameter : Type
– Funktionsresultat -> Type
– Variable : Type = , z.B. a : int = 10
� Für Type kann stehen:
– Basistyp: int, float, bool und str für String
– Klassennamen (nicht rekursiv die eigene Klasse)
– None
– Konstrukt aus Modul Typing
� Type hints sind für Python nur Hinweise. Einzig gute IDE werten diese aus:
22Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Type hints III – Modul Typing
� Siehe: https://docs.python.org/3/library/typing.html
� Zusammenfassung von Alternativen mittels Union
from typing import Union
def myFunc(param : int) -> Union[str,float]: if param>=0: return float(param) else: return "negative"
print(myFunc(3))print(myFunc(-5))
from typing import Optional
def myFunc(param: Optional[int]= -1): if param==-1: print(param) else: print('no parameter')
myFunc(3)myFunc()
Beispiel füroptionaleParameter
23Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Type hints IV – Modul Typing
� Angabe einer Liste und eines Dictionaries
from typing import List
def printNames(names: List[str]) -> None: for student in names: print(student)
printNames(['Alice','Bob'])
from typing import Dict
def printStudents(students: Dict[str, int]) -> None: for student, age in students.items(): print(student, age)
printStudents({"age": 22})
24Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Type hints V – Modul Typing
� Angabe eines Tupels
from typing import Tuple
def mod(a: int, b: int) -> Tuple[int, bool]: if b != 0: return a%b, True else: return 0, False
print(mod(16,5))print(mod(9,0))
(1, True)(0, False)
Output
25Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Type hints VI – __annotations__
� In der Eigenschaft __annotations__ sind die Type-Hints in Form eines Dictionaries abgespeichert.
from typing import Union
def myFunc(param : int) -> Union[str,float]: if param>=0: return float(param) else: return "negative"
print(myFunc.__annotations__)
{'param': <class 'int'>, 'return': typing.Union[str, float]}Output
26Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Style Guides
� Offiziell: https://www.python.org/dev/peps/pep-0008/
– Cheat Sheet: https://gist.github.com/RichardBronosky/454964087739a449da04
– Zusammenfassunghttp://gki.informatik.uni-freiburg.de/teaching/info1_guide/styleguide.html
� Auch
– http://google.github.io/styleguide/pyguide.html
27Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
print() mit %-Formatstring I
� Syntax 'Format-String' % (Tupel)
� In der Syntax %X wird der Typ sowie das Format angegeben: %[flags][width][.precision]type
� Für type sind üblich %d (int), %f (Float), %s (String), %c (char) und %% für %.
� Die Anzahl der % im Formatstring muss gleich der Anzahl der Elemente im Tupel sein.
print('%d mit mod %d' %(x,y))
Format-String
Werte ineinem Tupel
28Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
print() mit %-Formatstring II
� Das Ende der Zeile sowie die Zeichenkette zwischen den Bestandteilen kann frei definiert werden (Default):
� Syntax mit den Default-Werten:print(Objekte, sep=' ', end='\n')
� Siehe
– https://www.python-kurs.eu/python3_formatierte_ausgabe.php
print(2, end='')print('**',4, sep='', end=' ')print('=', 2**4)
Output 2**4 = 16
29Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
print() mit format() I
� Die Format-Funktion interpretiert den Formatstring und generiert einen Ausgabe-String, der auch in String-Variablen abgelegt werden kann.
� Die Platzhalter sind hier {}.
a= 10b= 3print('{} mod {} = {}'.format(a,b,a%b))c= '{} mod {} = {}'.format(a,b,a%b)print(c)
10 mod 3 = 110 mod 3 = 1
Output
30Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
print() mit format() II
� Die Platzhalter können benannt werden, so dass die Reihenfolge der Werte beliebig sein kann.
� Den Namen der Platzhalter werden dann die Werte zugeordnet.
student= {"Name": "Alice", "Age": 22}print('{name} ist {age} Jahre alt' \ .format(name= student["Name"], \ age= student["Age"]))
Alice ist 22 Jahre alt
Output
31Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
print() mit f-Strings I
� Die f-Strings haben Platzhalter, in denen Python-Konstrukte für den Zugriff auf Variablen stehen, also ganz ähnlich wie in PHP.
� Dadurch wird alles etwas kürzer.
� Wenn – wie in dem Beispiel – ein String innerhalb des Strings vorkommt, dann müssen die jeweils alternativen Stringbegrenzer (" oder ') benutzt werden.
� Die f-Strings können direkt hintereinander ohne die Aufhebung des Zeilenendes in mehreren Zeilen geschrieben werden.
student= {"Name": "Alice", "Age": 22}print(f"{student['Name']} ist {student['Age']} Jahre alt")print(f"{student['Name']} ist " f"{student['Age']} Jahre alt")
32Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
print() mit f-Strings II
� Es sind auch Aufrufe von Funktionen bzw. Ausdrücke innerhalb der {}-Klammern erlaubt.
student= {"Name": "Alice", "Age": 22}print(f"{student['Name'].upper()} ist " f"{student['Age']+2} Jahre alt")
ALICE ist 24 Jahre alt
Output
33Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Dokumentation I
� help(...) gibt die Signatur sowie den direkt darunter im String stehenden Text aus.
� Auf diesen String kann über die Eigenschaft __doc__ zugegriffen werden (ohne die Signatur).
� Siehe: https://www.python.org/dev/peps/pep-0257/
def toDoc(param: int) -> None: """ this function has a doc
Only one parameter of type int """
print(param)
help(toDoc)print('-------------------------')print(toDoc.__doc__)
Startzeile
Leerzeile
Beschreibung
Help on function toDoc in module __main__:
toDoc(param: int) -> None this function has a doc Only one parameter of type int
34Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Dokumentation II
� Diese so Doc-String genannten Beschreibungen stehen
– zum Beginn der Datei
– direkt hinter der class-Zeile
– direkt hinter der Funktionszeile (def)
35Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Datei I/O I - Beispiel
fn= "data.txt"ofile = open(fn, "w")for x in range(11): ofile.write(str(x**x)+"\n")ofile.close()ifile = open(fn, "r")s = ifile.read()print(s)
Output 114272563125466568235431677721638742048910000000000
fd= open(Filename,Mode) Eröffnen
fd.read() Lesen
fd.write Schreiben
fd.close() Schließen
fd.readline() 1 Zeile
fd.readlines() Alle Zeilen in eine Liste
36Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Datei I/O II - Beispiel
ifile = open("data.txt", "r")s = ifile.readline()print (s)s = ifile.readlines()print (s)
Output
Datei 114272563125466568235431677721638742048910000000000
1['1\n', '4\n', '27\n', '256\n', '3125\n','46656\n', '823543\n', '16777216\n','387420489\n', '10000000000\n']
37Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Exceptions I
� Das Verfahren in Python ist dem in PHP (und etwas dem von Java) sehr ähnlich.
� Es gibt den Try-Block (4) und den Catch-Block (6).
� Hinter Except steht dann der Name der Exception oder nichts mit der Bedeutung, dass alle Exceptions gemeint sind (5).
� Hinweis: Zeilen (4) und (6) führen zu verschiedenen Datentypen.
(1) a = 10(2) for b in [17, 0, -9]:(3) try:(4) d = a // b(5) except:(6) d = None(7) print(f"{a}//{b} = {d} ")
Output
10//17 = 0 10//0 = None 10//-9 = -2
38Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Exceptions II
� Es kann eine Kaskade von except-Blöcken, jeweils mit der Angabe der Exception an den Try-Block gehängt werden.
� Die except-Blöcke werden sequentiell geprüft; d.h. das Fangen aller Exceptions (10) muss immer am Ende stehen.
� Mit raise ohne Parameter wird die Exception weitergegeben.
(01) import sys(02) try:(03) fd = open('file42.txt')(04) line = fd.readline()(05) i = int(line.strip())(06) except IOError as e:(07) print(e)(08) except ValueError:(09) print("No integer data")(10) except:(11) print("Unknown error:", sys.exc_info()[0])(12) raise
39Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Exceptions III - Werfen
� Es wird eine von Exception abgeleitete Klasse definiert (1).
� Das pass in (2) bedeutet, dass dieser Teil leer ist.
� In (3) wird diese leere Klasse mit einem Erklärungsstring als ersten Parameter mit raise geworfen.
(1) class MyException(Exception):(2) pass
(3) raise MyException("Murphy!")
Output
Traceback (most recent call last): File "D:\bmesser\...\exception3.py", line 4, in <module> raise MyException("Murphy!")MyException: Murphy!
40Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Exceptions IV - Syntax
� Wie in Java/PHP wird der finally-Block immer ausgeführt und kann weggelassen werden.
� Die Catch-Blöcke können aus einem except-Block bestehen.
� Der else-Block wird nur dann ausgeführt, wenn keine Exception auftrat.
try: Block1except: Block2
try: Block1except ExceptionClass1: Block2except ExceptionClass2: Block3... except: BlockNfinally: BlockN+1
try: Block1except: Block2finally: Block3
try: Block1except: Block2else: Block3
try: Block1except: Block2else: Block3finally: Block4
41Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests I – für arme Leute
� Die Funktion berechnet eine Quersumme einer Zahl, die als String in Dezimalform gegeben ist.
� assert prüft, ob eine Bedingung erfüllt ist. Falls ja, passiert nichts, falls nein, wird eine Fehlermeldung mit dem 2. Parameter ausgegeben und das Programm abgebrochen.
def checksum(s: str) -> int: sum= 0 for c in s: sum+= int(c) return sum
def testCs(): assert checksum("123") == 6, "Should be 6" if __name__ == "__main__": testCs()print("All tests passed")
42Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests II – für arme Leute
� Die Fehlermeldung ist noch mit einem Stack-Trace, d.h. mit Informationen über die Aufruf-Situation versehen.
def checksum(s: str) -> int: sum= 1 for c in s: sum+= int(c) return sum
....
Fehler
Output Traceback (most recent call last): File "D:\bmesser\...", line 11, in <module> testCs() File "D:\bmesser\...", line 8, in testCs assert checksum("123") == 6, "Should be 6"AssertionError: Should be 6
43Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Zwischenbemerkung I
� __name__ hat den Namen des Moduls als Inhalt.
� Wird das Skript über die Kommandozeile aufgerufen, so ist der Modulname immer '__main__'.
� Wenn es aus einer Datei importiert wurde, dann enthält __name__ den Dateinamen.
if __name__ == "__main__": testCs()
Was bedeutet das?
if __name__ == '__main__': print('Das Skript läuft allein')else: print('Das Skript wurde importiert')
44Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Zwischenbemerkung II – noch etwas
� Das Modul sys realisiert den Zugriff auf die Umgebung.
– sys.argv ist eine Liste mit den Aufrufparametern
– sys.path ist eine Liste mit den Ordnern, die zum Laden durchsucht werden.
– sys.stout.write ist primitive print mit einem String.
import syssys.stdout.write('Die Parameter sind: ')for arg in sys.argv: sys.stdout.write(arg+' ')print('\n\n' 'Der PYTHONPATH ist',sys.path)
Zugriff auf dieParameterbei Aufruf im Shell
D:\bmesser\...0-Progs>python argv1.py a b cDie Parameter sind: argv1.py a b c
Der PYTHONPATH ist ['D:\\bmesser\\...0-Progs',... 'C:\\Program Files (x86)\\Python38-32', ... C:\\Program Files (x86)\\Python38-32\\lib\\site-packages']
D:\bmesser\...0-Progs>
Output
45Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests III – Modul unittest
� Zwei Dinge
– Eine Klasse, die testet
– Ein Modul bzw. eine Klasse, die getestet wird
(1) import unittest(2) from checksum import checksum
(3) class TestChecksum(unittest.TestCase):(4) def test1(self):(5) self.assertEqual(checksum("123"), 6, "Should be 6")(6) if __name__ == "__main__":(7) unittest.main()
def checksum(s: str) -> int: sum= 0 for c in s: sum+= int(c) return sum
Datei checksum.py
Datei ctest1.py
(A) .(B) -----------------------(C) Ran 1 test in 0.000s
(D) OK
Output
46Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Erläuterungen der Testklasse
� In (1) wird die Testumgebung importiert.
� In (2) wird das zu testende Modul bzw. Klasse importiert. Die erfolgt durch den Namen (ohne .py) einer Datei, die im Lade-Pfad (PYTHONPATH) liegt.
� In (3) wird die Testklasse als Unterklasse der Testumgebung (TestCase) definiert.
� Für jeden Test wird eine Methode geschrieben, in der über die assert-Routinen das Ergebnis eines Aufrufs geprüft wird.
� In (5) steht der assert-Aufruf.
� Der Testtreiber wird in (7) aufgerufen.
� Beim Output in Zeile (A) steht ein Zeichen für einen Test: . Bedeutet OK, F bedeutet Fail und E bedeutet sonstiger Fehler.
� Siehe: https://docs.python.org/3/library/unittest.html
47Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Reguläre Pakete I
� Um die Testdinge von den Realisierungsdingen zu trennen gibt es die Pakete (regular packages), die ähnlich zu Java sind.
� Ein Paket besteht aus den Dateien eines Ordners (im Pfad), der neben den Modulen und Klassen jeweils in Dateien eine Datei Namens __init__.py enthält.
� Beim Import des Paketes wird diese Datei ausgeführt. Dort stehen dann die Imports der Elemente des Paketes.
from .checksum import checksum
Datei __init__.py
Der Punkt istsehr wichtig
D:.| TestChecksum.py| \---crc checksum.py __init__.py
Test-Klasse
PaketDas Paket unseres Beispiels sollecrc heißen...
48Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Reguläre Pakete II
� In den Ordner crc kommen dann noch alle weiteren Realisierungen von Checksummen hinzu.
� In der Datei __init__.py wird gewissermaßen das Public-Interface des Pakets definiert. Alles, was hier nicht importiert wird, ist dann Paket-lokal.
import crc class TestChecksum(unittest.TestCase): def test1(self): self.assertEqual(crc.checksum("123"), ...)
from crc import *class TestChecksum(unittest.TestCase): def test1(self): self.assertEqual(checksum("123"), ...)
Mit dem Paket crc sehen die Testklassen so aus:
49Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests IV – Modul unittest
Funktion Erläuterung Negation
assertEqual(a, b) a == b assertNotEqual(a, b)
assertTrue(x) bool(x) is True assertFalse(x)
assertIs(a, b) a is b assertIsNot(a, b)
assertIsNone(x) x is None assertIsNotNone(x)
assertIn(a, b) a in b assertNotIn(a, b)
assertIsInstance(a, b) isinstance(a, b) assertNotIsInstance(a, b)
assertListEqual(a, b)
assertTupleEqual(a, b)
assertDictEqual(a, b)
Bei allen Routinen kann ein hier nicht angegebener String-Parameterzur Erläuterung des Tests hinten angefügt werden (Empfehlenswert).
50Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests V – Modul unittest Exceptions
� Die checksum-Routine mit einer Fehlerbehandlung.
class CRCException(Exception): pass
def checksumExcept(s: str) -> int: sum= 0 for c in s: if c.isdigit(): sum+= int(c) else: raise CRCException('no digit') return sum
51Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests VI – Modul unittest Exceptions
� Das ist das übliche Muster (Pattern) für Tests auf das Werfen von Exceptions.
� Fail() signalisiert, dass der Test schief geht.
� Bei der Behandlung der richtigen Exception steht pass.
import unittestfrom crc import *class TestExcept(unittest.TestCase): def test1(self): try: checksumExcept("A23") self.fail('CRCException exspected') except CRCException: pass except: self.fail('CRCException not raised')...
52Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests VII – Modul unittest Exceptions
� Das ist die verkürzte Version: hinter dem with steht der zu testende Aufruf.
� Der Parameter zu assertRaises ist der Name der Exception(-Klasse).
� With eröffnet einen Kontext für das, was hinter dem with steht.
� Siehe: https://docs.python.org/3/reference/compound_stmts.html
import unittestfrom crc import *
class TestExcept(unittest.TestCase): def test1(self): with self.assertRaises(CRCException): checksumExcept("A23")
53Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Unit-Tests VIII – Modul unittest
In jeder Testklasse gibt es noch die Möglichkeit für
setUp() wird vor jeder Testmethode aufgerufen und baut
das Fixture (Testumgebung) auf.
tearDown() baut das Fixture nach jeder Testmethode ab.
setUpClass() wird vor jeder Testklasse aufgerufen.
tearDownClass() wird nach allen Methoden einer Testklasse
aufgerufen.
skipTest(Grund) bewirkt das Auslassen eines Tests. Syntax:
https://docs.python.org/3/library/unittest.html#un
ittest-skipping
fail(Grund) signalisiert einen gescheiterten Test.
Der explizite Auf- und Abbau eines Fixtures (Umgebung) ist beiexterne Ressourcen (Dateien, Netze, Datenbanken) notwendig.Wichtig: Keine Seiteneffekte beim Testen!Alle Test können in beliebiger Reihenfolge ausgeführtwerden.
54Verteilte Anwendungen – SS 2020 – Teil P2/Objekte und mehr
Nun wieder etwas entspannen...