Kurs Python richtig lernen/Unit Testing: Unterschied zwischen den Versionen

Aus Geoinformation HSR
Wechseln zu: Navigation, Suche
K (Aufgabe 2 - Rationale Zahlen)
K
 
(19 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
 +
Zurück zum [[Kurs Python richtig lernen#Uebungen]].
 +
 
== Übung "Unit Testing mit Python und Nose" ==
 
== Übung "Unit Testing mit Python und Nose" ==
  
Zeile 4: Zeile 6:
  
 
Geschätzter Zeitaufwand für diese Übung: ca. '''2h'''.
 
Geschätzter Zeitaufwand für diese Übung: ca. '''2h'''.
 +
 +
Voraussetzungen: Python & Nose (Python Library) installiert.
  
 
=== Einstieg ===
 
=== Einstieg ===
Zeile 12: Zeile 16:
 
     assert 42 == 42
 
     assert 42 == 42
  
Führen Sie diesen Test wiefolgt aus:
+
Führen Sie diesen Test wie folgt aus:
  
 
  $ nosetests
 
  $ nosetests
  
Als Output bekommen Sie eine kleine Statistik inkl. dem Fehlerstatus (OK/NOK). Möchten Sie detailliertere Infors (welche Tests durchgeführt wurden), dann verwenden Sie die Option ''-v'' (verbose).
+
Als Output bekommen Sie eine kleine Statistik inkl. dem Fehlerstatus (OK/NOK). Möchten Sie detailliertere Infos (welche Tests durchgeführt wurden), dann verwenden Sie die Option ''-v'' für verbose (en. für "gesprächig").
  
Wie Sie sehen, müssen wir nirgends angeben, welche Funktionen und Module Nose als Testfunktionen betrachtet. Für Funktionen und Module gibt es eine einfache Namenskonvention, die durch folgende regular expression beschrieben ist: ((?:^|[b_.-])[Tt]est)
+
Wie Sie sehen, müssen wir nirgends angeben, welche Funktionen und Module Nose als Testfunktionen betrachtet. Für Funktionen und Module gibt es eine einfache Namenskonvention (Abmachung), wobei verlangt wird, dass der Filename mit ''test'' oder ''Test'' beginnen muss (die exakte Definition ist eine Regular Expression  und sieht so aus: ''((?:^|[b_.-])[Tt]est)'').
  
Manchmal benötigen Sie für Ihre Unit Tests Fixtures. Diese können Sie wiefolgt definieren:
+
Manchmal benötigen Sie für Ihre Unit Tests ''Fixtures''. Diese können Sie wie folgt definieren:
  
  def test_life_the_universe_and_everything():
+
  <nowiki>
    assert 42 != 42
+
def test_life_the_universe_and_everything():
 +
    assert 42 != 42
 
   
 
   
def setUp():  
+
def setUp(): print "Setup"
    print "Setup"
+
def tearDown(): print "tearDown"
def tearDown():  
+
 
    print "tearDown"
+
test_life_the_universe_and_everything.setUp = setUp
test_life_the_universe_and_everything.setUp = setUp
+
test_life_the_universe_and_everything.tearDown = tearDown
test_life_the_universe_and_everything.tearDown = tearDown
+
</nowiki>
  
 
Führen Sie dieses Beispiel aus und verifizieren Sie, dass die Ausgaben Ihren Erwartungen entsprechen (setup, failing test, teardown).
 
Führen Sie dieses Beispiel aus und verifizieren Sie, dass die Ausgaben Ihren Erwartungen entsprechen (setup, failing test, teardown).
  
Nose erlaubt es Ihnen - neben Testfunktionen - Ihre Tests auch innerhalb einer Klasse zu schreiben (gemäss xUnit). Das gleiche Beispiel wie vorhin:
+
Nose erlaubt es Ihnen - neben Testfunktionen - Ihre Tests auch innerhalb einer Klasse zu schreiben (gemäss Unit Testing Konvention 'xUnit', hier bei Python [http://docs.python.org/library/unittest.html unittest]). Das gleiche Beispiel wie vorhin:
 +
 
 +
<nowiki>
 +
class Test42:
 +
    def setUp(self):
 +
        print "Setup"
  
class Test42:
+
    def tearDown(self):
    def setUp(self):
+
        print "tearDown"
        print "Setup"
+
 
    def tearDown(self):
+
    def test_life_the_universe_and_everything(self):
        print "tearDown"
+
        assert 42 != 42
    def test_life_the_universe_and_everything(self):
+
</nowiki>
        assert 42 != 42
 
  
 
''Tip'': Wundern Sie sich nicht, wenn Sie Ausgaben in Ihren Unit Tests mit ''print'' nicht auf Ihrer Konsole sehen. Nose fängt alle Ausgaben auf ''stdout'' auf und zeigt diese nur bei fehlgeschlagenen Tests an. Dieses Verhalten kann mit der Option ''-s'' unterbunden werden.
 
''Tip'': Wundern Sie sich nicht, wenn Sie Ausgaben in Ihren Unit Tests mit ''print'' nicht auf Ihrer Konsole sehen. Nose fängt alle Ausgaben auf ''stdout'' auf und zeigt diese nur bei fehlgeschlagenen Tests an. Dieses Verhalten kann mit der Option ''-s'' unterbunden werden.
  
Um das Werfen von Exceptions testen zu können, benutzen Sie folgende Nose Decoration:
+
Um das Werfen von Exceptions testen zu können, benutzen Sie folgende Nose Decoration (siehe die Zeile mit ''@''):
  
 +
<nowiki>
 
  from nose.tools import raises
 
  from nose.tools import raises
  @raises(ZeroDivisionError)
+
 
 +
  @raises(ValueError)
 
  def test_rational_zero_denominator_raises_error():
 
  def test_rational_zero_denominator_raises_error():
 
     r = Rational(3, 0)
 
     r = Rational(3, 0)
 +
</nowiki>
  
 +
Weitere Informationen zu Nose finden Sie [http://nose.readthedocs.org/en/latest/testing.html hier].
  
==== Aufgabe 1 - Bank Account ====
+
=== Aufgabe 1 - Bank Account ===
 
Testen Sie folgenden Code mit Nose (denken Sie auch an das Testen von Exceptions):
 
Testen Sie folgenden Code mit Nose (denken Sie auch an das Testen von Exceptions):
  
Zeile 70: Zeile 83:
  
  
==== Aufgabe 2 - Rationale Zahlen ====
+
=== Aufgabe 2 - Rationale Zahlen ===
 
Testen Sie folgenden Code mit Nose (denken Sie auch an das Testen von Exceptions):
 
Testen Sie folgenden Code mit Nose (denken Sie auch an das Testen von Exceptions):
  
Zeile 80: Zeile 93:
 
         return gcd(b, a % b)
 
         return gcd(b, a % b)
  
class Rational:
+
 
 +
class Rational(object):
 +
   
 
     def __init__(self, n, d):
 
     def __init__(self, n, d):
 
         if d == 0:
 
         if d == 0:
Zeile 88: Zeile 103:
 
             self.n = n / g
 
             self.n = n / g
 
             self.d = d / g
 
             self.d = d / g
+
         
 
     def __add__(self, other):
 
     def __add__(self, other):
 
         return Rational(self.n * other.d + other.n * self.d,
 
         return Rational(self.n * other.d + other.n * self.d,
Zeile 96: Zeile 111:
 
         return Rational(self.n * other.d - other.n * self.d,
 
         return Rational(self.n * other.d - other.n * self.d,
 
                         self.d * other.d)
 
                         self.d * other.d)
       
+
     
 
     def __mul__(self, other):
 
     def __mul__(self, other):
         return Rational(self.n * other.n, self.d * other.d)
+
         return Rational(self.n * other.n, self.d * other.d)
   
+
 
 
     def __div__(self, other):
 
     def __div__(self, other):
         return Rational(self.n * other.d, self.d * other.n)
+
         return Rational(self.n * other.d, self.d * other.n)
   
+
 
 
     def __str__(self):
 
     def __str__(self):
 
         return "%d/%d" % (self.n, self.d)
 
         return "%d/%d" % (self.n, self.d)
 
    
 
    
 
     def __float__(self):
 
     def __float__(self):
         return float(self.n) / float(self.d)
+
         return float(self.n) / float(self.d)
 
      
 
      
 
     def __eq__(self, other):
 
     def __eq__(self, other):
Zeile 116: Zeile 131:
 
         else:
 
         else:
 
             return self.n == other.n and self.d == other.d
 
             return self.n == other.n and self.d == other.d
 +
       
 +
    def __ne__(self, other):
 +
        return not self.__eq__(other)
 
</nowiki>
 
</nowiki>

Aktuelle Version vom 17. Januar 2013, 16:08 Uhr

Zurück zum Kurs Python richtig lernen#Uebungen.

Übung "Unit Testing mit Python und Nose"

In dieser Übung lernen Sie Unit Testing mit dem Python Test Runner Nose.

Geschätzter Zeitaufwand für diese Übung: ca. 2h.

Voraussetzungen: Python & Nose (Python Library) installiert.

Einstieg

Um Nose kennenzulernen starten wir mit einem Beispiel. Speichern Sie den folgenden Unit test in einem File "test_42.py":

def test_life_the_universe_and_everything():
    assert 42 == 42

Führen Sie diesen Test wie folgt aus:

$ nosetests

Als Output bekommen Sie eine kleine Statistik inkl. dem Fehlerstatus (OK/NOK). Möchten Sie detailliertere Infos (welche Tests durchgeführt wurden), dann verwenden Sie die Option -v für verbose (en. für "gesprächig").

Wie Sie sehen, müssen wir nirgends angeben, welche Funktionen und Module Nose als Testfunktionen betrachtet. Für Funktionen und Module gibt es eine einfache Namenskonvention (Abmachung), wobei verlangt wird, dass der Filename mit test oder Test beginnen muss (die exakte Definition ist eine Regular Expression und sieht so aus: ((?:^|[b_.-])[Tt]est)).

Manchmal benötigen Sie für Ihre Unit Tests Fixtures. Diese können Sie wie folgt definieren:

def test_life_the_universe_and_everything():
    assert 42 != 42
 
def setUp(): print "Setup"
def tearDown(): print "tearDown"

test_life_the_universe_and_everything.setUp = setUp
test_life_the_universe_and_everything.tearDown = tearDown

Führen Sie dieses Beispiel aus und verifizieren Sie, dass die Ausgaben Ihren Erwartungen entsprechen (setup, failing test, teardown).

Nose erlaubt es Ihnen - neben Testfunktionen - Ihre Tests auch innerhalb einer Klasse zu schreiben (gemäss Unit Testing Konvention 'xUnit', hier bei Python unittest). Das gleiche Beispiel wie vorhin:

class Test42:
    def setUp(self): 
        print "Setup"

    def tearDown(self):
        print "tearDown"

    def test_life_the_universe_and_everything(self):
        assert 42 != 42

Tip: Wundern Sie sich nicht, wenn Sie Ausgaben in Ihren Unit Tests mit print nicht auf Ihrer Konsole sehen. Nose fängt alle Ausgaben auf stdout auf und zeigt diese nur bei fehlgeschlagenen Tests an. Dieses Verhalten kann mit der Option -s unterbunden werden.

Um das Werfen von Exceptions testen zu können, benutzen Sie folgende Nose Decoration (siehe die Zeile mit @):

 from nose.tools import raises

 @raises(ValueError)
 def test_rational_zero_denominator_raises_error():
     r = Rational(3, 0)

Weitere Informationen zu Nose finden Sie hier.

Aufgabe 1 - Bank Account

Testen Sie folgenden Code mit Nose (denken Sie auch an das Testen von Exceptions):

class BankAccount(object):
    def __init__(self, initial_balance=0):
        self.balance = initial_balance 
    
    def deposit(self, amount):
        self.balance += amount      

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError('Amount is higher than current balance')
        self.balance -= amount


Aufgabe 2 - Rationale Zahlen

Testen Sie folgenden Code mit Nose (denken Sie auch an das Testen von Exceptions):

def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)


class Rational(object):
    
    def __init__(self, n, d):
        if d == 0:
            raise ValueError("Denominator must not be zero.")
        else:
            g = gcd (n, d)
            self.n = n / g
            self.d = d / g
           
    def __add__(self, other):
        return Rational(self.n * other.d + other.n * self.d,
                        self.d * other.d)
       
    def __sub__(self, other):
        return Rational(self.n * other.d - other.n * self.d,
                        self.d * other.d)
       
    def __mul__(self, other):
        return Rational(self.n * other.n, self.d * other.d)
   
    def __div__(self, other):
        return Rational(self.n * other.d, self.d * other.n)
   
    def __str__(self):
        return "%d/%d" % (self.n, self.d)
   
    def __float__(self):
        return float(self.n) / float(self.d)
    
    def __eq__(self, other):
        if self is other:
            return True
        elif type(self) != type(other):
            return False
        else:
            return self.n == other.n and self.d == other.d
        
    def __ne__(self, other):
        return not self.__eq__(other)