Python Unit Test: assert, unittest & PyTest

    Avatarbild von Dr.-Ing. Soner Emec
    Dr.-Ing. Soner Emec

    veröffentlicht am: 10.06.2022
    zuletzt aktualisiert am: 01.02.2023

    Python Unit Test: So baust du solide Python Programme!

    In diesem Tutorial geht es darum, das Konzept der Python Unit Test zu verstehen.

    „Aber auf meinem Gerät hat es gut funktioniert“, ist ein häufiges Zitat von Entwicklern, das ein Resultat schlechter Programmiertechniken ist. Es kommt vielfach vor, dass ein bestimmter Code während der Entwicklung oder in der Produktionsumgebung aufgrund mangelnder Tests und fehlender Eingabevalidierungen fehlschlägt. Um solche Situationen zu verbessern und Störungen in der Software zu vermeiden, schreiben Programmierer/innen Unit-Tests, um ihren Code zu validieren.

    Unit-Tests sind Tests, die dabei helfen, die Funktionalität eines bestimmten Teils eines Computerprogramms zu verifizieren. Sie unterstützen den Programmierer, zu verstehen, ob ein Teil des Codes wie vorgesehen funktioniert. Diese Tests werden in der Regel an Einheiten durchgeführt, die je nach Programmiersprache auch als Funktionen oder Prozeduren bezeichnet werden. Einer der am häufigsten verwendeten Ansätze für Unit-Tests ist der Test-Funktions-Wunschwert-Ansatz. Bei diesem Ansatz vergleicht ein Unit-Test die Ausgabe einer Funktion mit dem gewünschten Wert. Wenn beide Werte übereinstimmen, gilt der Test als erfolgreich. Schlägt der Test hingegen fehl, wird eine Fehlermeldung angezeigt und die Ausgabe protokolliert.

    Versuchen wir dies mithilfe eines Beispiels zu verstehen. Nehmen wir an, wir haben einen Code geschrieben, der die Summe der Werte eines Eingabe-Arrays berechnet. Das könnten wir in Python etwa mit der Funktion sum() umsetzen. Um nun zu testen, ob unsere sum()-Funktion einen bestimmten Wert liefert oder nicht, können wir einen Unit-Test verwenden. Setzen wir diesen Wert auf 15. Unser Unit-Test wird nur dann als erfolgreich gewertet, wenn die Summe der Werte im Python Array 15 beträgt. Wenn nicht, wird eine Fehlermeldung ausgegeben, die besagt, dass der gewünschte Wert nicht gefunden wurde.

    Mithilfe des nachfolgenden Codes möchten wir das gern zeigen:

    def unit_tester():  
        assert sum([4, 6, 5]) == 15, "Die Summe aller Zahlen sollte 15 sein."  
      
    if __name__ == "__main__":  
        unit_tester()  
        print("Korrekte Summe!")  

    Wie wir sehen können, haben wir eine Funktion namens unit_tester() erstellt, die als Unit-Test dient, um zu prüfen, ob eine Python List einen bestimmten Wert erreicht (in unserem Fall 15). Wir verwenden das Schlüsselwort assert in Python

    Der oben genannte Code würde die nachfolgende Ausgabe produzieren:

    Wir können Python assert verwenden, um unsere Methoden schnell zu testen!

    Wie wir anhand der Ausgabe sehen können, wird die Meldung „Korrekte Summe“ angezeigt. Das liegt daran, dass die Werte im Array [4,6,5] in Summe den Wert 15 ergeben (4 + 6 + 5).

    Versuchen wir nun herauszufinden, was passieren würde, wenn die Summe unseres Arrays nicht den gewünschten Wert von 15 ergeben würde. Verwenden wir ein anderes Array mit den Werten [4, 6, 4].

    def unit_tester():  
        assert sum([4, 6, 4]) == 15, "Die Summe aller Zahlen sollte 15 sein."  
      
    if __name__ == "__main__":  
        unit_tester()  
        print("Korrekte Summe!")  

    Wie wir sehen können, ist die Summe des neuen Arrays 14 (4 + 6 + 4). Der oben genannte Codeblock würde damit nun folgendes ausgeben:

    Dieser Test ist fehlgeschlagen, da die Bedingung nicht mehr erfüllt ist!

    Wie wir sehen können, wird ein Assertion-Fehler ausgelöst (aufgrund des Schlüsselworts assert), der unsere Fehlermeldung ausgibt. Mit dieser einfachen Demonstration einer Testfunktion können wir das grundlegende Konzept von Unit-Tests verstehen.

    Unsere Funktion unit_tester() berücksichtigt jedoch nicht alle Fehler, die durch den Unit-Test ausgelöst werden können. Daher verwenden wir eine Bibliothek zur Testausführung, um weitere Fehler zu vermeiden. Diese Bibliotheken dienen als Hilfsmittel, um Tests auszuführen, Fehler zu finden und zu beheben. Einige der am häufigsten verwendeten Bibliotheken in Python, die bei Unit-Tests helfen, sind folgende:

    1. Unittest
    2. PyTest

    Wir möchten uns diese Bibliotheken nun näher anschauen und ihre Verwendung anhand von Beispielen verstehen:

    Python Unit Test I: Unittest

    Python unittest ist ein Test-Framework in Python, das bei der Automatisierung, dem Export-Setup, dem Testen einzelner Komponenten sowie bei der Fehlersuche hilft. Es ist weitverbreitet und dient vielen Konzepten der objektorientierten Programmierung wie einzelnen Testfällen, einer Sammlung von Testfällen als Suite, der Erstellung von Dummy-Daten für Tests und vielem mehr.

    Wenn du mit unittest arbeitest, musst du eine Unterklasse wie unittest.TestCase erstellen. Außerdem muss der Programmierer für jeden Test den Namen der Testfunktion mit test… beginnen. Um etwa einen Test zur Überprüfung von Großbuchstaben zu erstellen, kann der Funktionsname test_upper oder test_something lauten. Zum Schluss müssen wir je nach Anforderung drei verschiedene assert-Anweisungen aufrufen.

    • assertEqual – um zu prüfen, ob der Parameter mit dem gewünschten Wert übereinstimmt
    • assertTrue() oder assertFalse() – um zu prüfen, ob ein bestimmtes Kriterium innerhalb der Testfunktion erfüllt ist oder nicht
    • assertRaises() – um sicherzustellen, dass ein bestimmter Fehler ausgelöst wird

    Versuchen wir, jede dieser Funktionen anhand eines einfachen Beispiels zu verstehen.

    Angenommen, wir arbeiten mit Strings und möchten drei Tests schreiben, die überprüfen, ob ein String groß- oder kleingeschrieben ist und ob ein bestimmtes Ergebnis beim Aufteilen eines Strings herauskommt oder nicht. Das können wir mithilfe des folgenden Codes in Python tun:

    # Ein Unit-Test in Python
    # A unit test in Python
    
    import unittest
    
    class StringTesters(unittest.TestCase):
    
        def test_upperCase(self):
            self.assertEqual('Preet'.upper(), 'PREET')
    
        def test_isupperCheck(self):
            self.assertTrue('PREET'.isupper())
            self.assertFalse('Preet'.isupper())
    
        def test_checkSplits(self):
            s = 'I love food'
            self.assertEqual(s.split(), ['I', 'love', 'food'])
            # Sicherstellen, dass s.split fehlschlägt, wenn das Trennzeichen kein String ist
            with self.assertRaises(TypeError):
                s.split(2)
    
    if __name__ == '__main__':
        unittest.main()

    Wie wir sehen können, haben wir drei Funktionen mit jeweils einem Test. Die erste Funktion test_uppercase prüft, ob die Zeichenkette Preet mit Großbuchstaben zu PREET wird. Die zweite Funktion test_isupperCheck bestätigt mit True oder False , je nachdem, welche Bedingung erfüllt ist. Die dritte Funktion test_checkSplits prüft, ob die Zeichenkette I love food in ein Array [‚I‘, ‚love‘, ‚food‘] zerlegt wird, wenn sie an die split()-Methode in Python übergeben wird.

    Die Ausgabe des oben genannten Codes kann wie folgt aussehen:

    So kannst du Python unittest verwenden, um gute Unit Tests in Python zu schreiben!

    Das . in der Ausgabe bedeutet, dass die Tests erfolgreich durchgeführt wurden. Versuchen wir nun, den folgenden Code auszuführen:

    import unittest
    
    class StringTesters(unittest.TestCase):
    
        def test_upperCase(self):
            self.assertEqual('Preet'.upper(), 'PREET')
    
        def test_isupperCheck(self):
            self.assertTrue('PREET'.isupper())
            self.assertFalse('Preet'.isupper())
    
        def test_checkSplits(self):
            s = 'I love foo'
            # Hierbei wird uns s nicht das gewünschte Resultat geben
            self.assertEqual(s.split(), ['I', 'love', 'food'])
            # Sicherstellen, dass s.split fehlschlägt, wenn das Trennzeichen kein String ist
            with self.assertRaises(TypeError):
                s.split(2)
    
    if __name__ == '__main__':
        unittest.main()

    Wie wir sehen können, würde die Python split Funktion nun einen Fehler auslösen, da der Split nicht das gewünschte Ergebnis liefert.

    Die Ausgabe des obigen Codes könnte wie folgt aussehen:

    F..
    ======================================================================
    FAIL: test_checkSplits (__main__.StringTesters)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "C:/Users/Preet/AppData/Roaming/JetBrains/PyCharmCE2020.3/scratches/scratch.py", line 15, in test_checkSplits
        self.assertEqual(s.split(), ['I', 'love', 'food'])
    AssertionError: Lists differ: ['I', 'love', 'foo'] != ['I', 'love', 'food']
    
    First differing element 2:
    'foo'
    'food'
    
    - ['I', 'love', 'foo']
    + ['I', 'love', 'food']
    ?                   +
    
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.016s
    
    FAILED (failures=1)
    # The test: The following test case failed
    # You have a list of a different test method

    Wie wir sehen können, erhalten wir ein F in der Ausgabe, wenn ein Test fehlschlägt. Das hilft uns, den Fehler zu finden und ihn zu beheben.

    Python Unit Test II: PyTest

    Pytest funktioniert auf ähnliche Weise wie Unittest. Allerdings muss man sich bei pytest nicht die Namen der Funktionen merken, die aufgerufen werden sollen. Außerdem muss man in pytest im Gegensatz zu unittest kein Paket importieren oder eine Unterklasse erstellen. Pytest unterstützt auch objektorientierte Funktionen, die denen von Unittest ähneln, um das Debuggen und die Fehlersuche zu erleichtern. Lass uns versuchen zu verstehen, wie man Pytest verwenden kann.

    Hinweis: Um Pytest ausführen zu können, muss Python 3.7 oder höher installiert sein.

    Zuerst installieren wir die notwendigen Abhängigkeiten. Das können wir mithilfe des folgenden Befehls tun:

    pip install -U pytest

    Nun erstellen wir eine Datei test_sample.py mit dem folgenden Code:

    def func(x):
        return x + 2
    
    def test_answer():
        assert func(2) == 5

    Wenn wir nun pytest mit dieser Datei in der Eingabeaufforderung ausführen, erhalten wir das folgende Ergebnis:

    test_sample.py F                                                     [100%]
    
    ================================= FAILURES =================================
    _______________________________ test_answer ________________________________
    
        def test_answer():
    >       assert func(3) == 5
    E       assert 4 == 5
    E        +  where 4 = func(3)
    
    test_sample.py:6: AssertionError
    ========================= short test summary info ==========================
    FAILED test_sample.py::test_answer - assert 4 == 5
    ============================ 1 failed in 0.12s =============================

    Wie wir sehen können, wird mit pytest neben den einzelnen Testergebnissen ebenfalls eine kurze Zusammenfassung erstellt. Das ist besonders praktisch, wenn du mit mehreren Tests oder einer Testsuite arbeitest.

    Jetzt, wo wir wissen, wie wir Unit-Tests verwenden, wollen wir auch verstehen, wann wir sie einsetzen sollten.

    Zusammenfassung: Unit-Tests Python

    Unit-Tests sind in einer Softwareentwicklungsumgebung essenziell. Sie sind ein integraler Bestandteil von DevOps und wurden kürzlich auch in MLOps integriert. Vereinzelt verwenden Tech-Giganten Unit-Tests schon vor der Entwicklung der Software, wenn das gewünschte Ergebnis bekannt ist. Das hilft dabei, Code zu schreiben und direkt in die Produktionsumgebung zu übertragen, sobald er die vorab geschriebenen Tests besteht. Unit-Tests sind das wichtigste Konzept, das potenzielle Qualitätssicherungsingenieure lernen und verstehen müssen.

    Vorteile vom Python Unit Test

    Zusammenfassend lassen sich die Vorteile von Unit-Tests also wie folgt auflisten:

    • Unit-Tests bieten eine unglaublich schnelle Rückmeldung im Vergleich zu anderen Testarten
    • Sie helfen dabei, Regressionsfehler zu vermeiden. Regressionsfehler sind im Grunde genommen Fehler, die in die Software eingeschleust werden, wenn sie in die Produktion überführt wird.
    • Unit-Tests helfen, die Effizienz und Robustheit beim Schreiben von Code zu fördern. Programmierer neigen in der Regel dazu, Randfälle zu übersehen, die von den Unit-Tests wiederum erkannt werden.
    • Es erleichtert die Fehlersuche und ermöglicht Programmierern, ihren Code effizient zu überarbeiten.

    Nachteile vom Python Unit Test

    Unit-Tests helfen uns zwar dabei, Code zu schreiben und ihn sicher in die Produktion zu überführen, doch sie haben auch ein paar Nachteile, etwa:

    • Unit-Tests sind ein zeitaufwendiger Prozess. Es braucht viel Zeit, um für jede einzelne Komponente des Codes einen Test zu schreiben.
    • Unit-Tests können nicht alle Fehler abfangen, die potenziell im Code enthalten sind.
    • Die Pflege und Aktualisierung eines Unit-Tests sind extrem mühsame Prozesse, da es dafür weitere Tests und Arbeit erfordert.

    Mithilfe dieser Erklärungen haben wir nun ein gutes Verständnis dafür, was Unit-Tests sind, wie sie in Python implementiert werden und welche Vor- und Nachteile sie haben.

    Falls du mehr über Python lernen möchtest, schau dir unsere große Python-Bibliothek an!

    😩 Gelangweilt von den Udemy & YouTube-Tutorials?!

    Lerne spielerisch Python und komme deiner gutbezahlten (und an der 🌴 liegenden) Traumkarriere einen Schritt weiter.

    TP Star TP Star TP Star TP Star TP Star

    "Für Leute die gerne Python oder Java lernen wollen ist Codegree klasse. Ist nicht wie bei anderen Konkurrenten auf Videokursen aufgebaut..."

    - Lennart Sparbier

    100% kostenlos registrieren · keine Kreditkarte notwendig

    👋 Wir warten bereits auf dich!

    Lerne das, was du wirklich brauchst.

    Im Gegensatz zu der Abendschule oder der alteingesessenen Uni lernst du bei codegree die Sprachen & Pakete, die wirklich im Jobmarkt gesucht werden.

    Logo von Python
    Logo von PyTorch
    Logo von Pandas
    Logo von Matplotlib
    Logo von Java
    Logo von NumPy
    Mehr erfahren

    100% kostenlos registrieren · keine Zahlungsdaten notwendig