Python Unittest vs. Pytest

Salman Mehmood 10 Oktober 2023
Python Unittest vs. Pytest

Das Hauptziel dieses Artikels ist es, zwei der am häufigsten verwendeten Frameworks für Unit-Tests in Python zu diskutieren, unittest und pytest, ihre Vor- und Nachteile und wann man welches dem anderen vorzieht.

Python unittest gegen Pytest

Beim Schreiben von Software müssen wir den Prozess der Fehlerprüfung während des gesamten Entwicklungsprozesses aufrechterhalten. Es stellt sicher, dass, sobald die Software das Release-Stadium erreicht, während ihrer Verwendung eine minimale Anzahl von Fehlern auftritt.

Python verfügt auch über eine Vielzahl von Test-Frameworks, die das Testen von geschriebenem Code ermöglichen, indem ihm verschiedene Eingaben zur Überprüfung seines Verhaltens gegeben werden.

Falls ein Fehler auftritt, kann er während der Entwicklungsphase behoben werden, im Gegensatz zu hotfixes nach der ersten Veröffentlichung der Anwendung.

Beispielcode:

class Calculate:
    def CheckPrime(self, a):
        for i in range(a):
            if a % i:
                return False
        return True

    def CalcFact(self, a):
        if a == 1:
            return a
        else:
            return a * self.fact(a - 1)

Der oben gezeigte Code enthält zwei Funktionen namens CheckPrime und CalcFact, die, wie aus ihren Namen ersichtlich ist, nach Primzahlen suchen und Fakultäten berechnen.

Damit die Berechnen-Methoden reibungslos funktionieren, ist die Überprüfung auf Fehler, die durch unterschiedliche Ausgaben auftreten können, unerlässlich.

Also, wie können wir das tun? Um sicherzustellen, dass unser Code fehlerfrei ist, können wir verschiedene Test-Frameworks verwenden, um Testfälle zu schreiben und unseren Code darüber hinaus zu testen, um die Integrität unseres Codes zu überprüfen.

Obwohl es viele Test-Frameworks gibt, sind zwei der am weitesten verbreiteten unittest und pytest. Lassen Sie uns sie unten einzeln untersuchen.

Unit Test durch unittest Framework

unittest ist ein Unit-Testing-Framework, das in der Python-Standardbibliothek enthalten ist. Dieses Framework wurde von JUnit inspiriert, einem Java-Framework für Unit-Tests.

Bevor Sie die Funktionsweise von unittest besprechen, ist es wichtig, häufig verwendete Begriffe in unittest (auch in anderen verwandten Frameworks verwendet) zu kennen.

  • Test Case – Kleinste Testeinheit – Besteht in der Regel aus einem einzigen
  • Test Suite – Testfälle gruppiert – Üblicherweise nacheinander ausgeführt
  • Test Runner – Koordiniert und verwaltet die Ausführung von Testfällen und Suiten

Verwenden Sie das unittest-Framework, um Testfälle zu schreiben

Da die Standardbibliothek von Python bereits unittest enthält, muss kein externes Modul heruntergeladen werden, um Unit-Tests mit unittest zu schreiben.

Nach dem Import des Moduls unittest kann es losgehen. Konzentrieren wir uns nun auf den Code, den wir zuvor durchgegangen sind.

Beispielcode:

class Calculate:
    def CheckPrime(self, a):
        for i in range(a):
            if a % i:
                return False
        return True

    def CalcFact(self, a):
        if a == 1:
            return a
        else:
            return a * self.fact(a - 1)

Um Testfälle mit unittest zu schreiben, müssen wir einer bestimmten Syntax folgen, nämlich dass die Testklasse ein Kind von unittest.TestCase ist und ihre Methoden mit test_ beginnen müssen.

Betrachten Sie den folgenden Code:

import unittest


class Calculate:
    def CheckPrime(self, a):
        for i in range(2, a):
            if a % i == 0:
                return False
        return True

    def CalcFact(self, a):
        if a == 1:
            return a
        else:
            return a * self.CalcFact(a - 1)


class TestCalc(unittest.TestCase):
    def test_CheckPrime(self):
        calc = Calculate()

        # Passing different outputs
        self.assertEqual(calc.CheckPrime(2), True)
        self.assertEqual(calc.CheckPrime(3), True)

        self.assertEqual(calc.CheckPrime(4), False)
        self.assertEqual(calc.CheckPrime(80), False)

    def test_CheckFact(self):
        calc = Calculate()

        # Passing different outputs
        self.assertEqual(calc.CalcFact(2), 2)
        self.assertEqual(calc.CalcFact(3), 6)

Ausgang:

PS D:\Unittest> python -m unittest a.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK 

Der Ausgabe nach zu urteilen, können wir sehen, dass alle Testfälle bestanden wurden, weil alle Behauptungen erfolgreich waren.

Versuchen wir nun einen Fall, in dem der Testfall fehlschlägt.

def test_CheckFact(self):
    calc = Calculate()

    # Passing different outputs
    self.assertEqual(calc.CalcFact(2), 2)
    self.assertEqual(calc.CalcFact(3), 6)
    # Supposed to throw an error
    self.assertEqual(calc.CalcFact(0), 0)

Ausgang:

PS D:\Unittest> python -m unittest a.py
======================================================================
ERROR: test_CheckFact (a.TestCalc)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\Python Articles\a.py", line 34, in test_CheckFact
    self.assertEqual(calc.CalcFact(0), 0) # Supposed to throw an error
  File "D:\Python Articles\a.py", line 15, in CalcFact
    return a * self.CalcFact(a-1)
  File "D:\Python Articles\a.py", line 15, in CalcFact
    return a * self.CalcFact(a-1)
  File "D:\Python Articles\a.py", line 15, in CalcFact
    return a * self.CalcFact(a-1)
  [The previous line is repeated 974 more times]
  File "D:\Python Articles\a.py", line 12, in CalcFact
    if (a == 1):
RecursionError: maximum recursion depth exceeded in comparison

----------------------------------------------------------------------
Ran 2 tests in 0.004s
FAILED (errors=1)

Wie aus dem Code hervorgeht, führen wir das Skript mit python -m unittest <name_of_script.py> aus.

Dieser Code funktioniert, ohne die Methoden der Testklasse aufzurufen, da das unittest-Modul Skriptdateien verarbeitet, die ihm in einem bestimmten Format übergeben werden.

Da unser Skript TestCalc enthielt, wird die Kindklasse von unittest.TestCase automatisch vom Test Runner instanziiert.

Nach der Instanziierung werden Testmethoden innerhalb der Klasse gefunden und der Reihe nach ausgeführt. Damit eine Methode als Testmethode betrachtet werden kann, muss sie mit einem test_ beginnen.

Sobald die Testmethoden gefunden sind, werden sie der Reihe nach aufgerufen; in unserem Fall werden sowohl test_CheckPrime als auch test_CalcFact aufgerufen. Zusicherungen werden in unserer Implementierung überprüft und bei unerwartetem Verhalten wird ein Fehler in die Ausgabe geworfen.

Aus unserem Testfall, der einen Fehler enthielt, kann man ableiten, dass aufgrund der Code-Schreibweise eine unendliche Rekursion in der Methode CalcFact gestartet wurde, die nun dank des Testfalls behoben werden kann.

Falls Sie sich fragen, warum der Fehler auftritt, liegt dies daran, dass die Anfangsbedingung nicht nach Zahlen kleiner als eins sucht.

Vor- und Nachteile des Frameworks unittest

Einige der Vorteile der Verwendung von unittest sind unten aufgeführt:

  • In der Python-Standardbibliothek enthalten
  • Fördert verwandte Testfälle in einer einzigen Testsuite
  • Schnelle Testsammlung
  • Präzise Testzeitdauer

Der unittest hat folgende Nachteile:

  • Kann schwer zu verstehen sein
  • Keine farbige Ausgabe
  • Kann zu ausführlich sein

Unit-Test durch das Pytest-Framework

Anders als unittest ist Pytest kein eingebautes Modul; wir müssen es separat herunterladen. Die Installation von Pytest ist jedoch relativ einfach; Dazu können wir pip verwenden und den folgenden Befehl ausführen.

pip install pytest

Verwenden Sie Pytest, um Testfälle zu schreiben

Lassen Sie uns einige Testfälle mit Pytest schreiben. Bevor wir jedoch beginnen, schauen wir uns an, wie sich Pytest von unittest beim Schreiben von Testfällen unterscheidet. Für in Pytest geschriebene Unit-Tests müssen wir:

  • Erstellen Sie ein separates Verzeichnis und legen Sie die zu testenden Skripte in das neu erstellte Verzeichnis.
  • Schreiben Sie Tests in Dateien, die entweder mit test_ beginnen oder mit _test.py enden. Ein Beispiel wäre test_calc.py oder calc_test.py.

Betrachten Sie den folgenden Code, der für Testfälle mit Pytest geschrieben wurde.

def test_CheckPrime():
    calc = Calculate()

    # Passing different outputs
    assert calc.CheckPrime(2) == True
    assert calc.CheckPrime(3) == True

    assert calc.CheckPrime(4) == False
    assert calc.CheckPrime(80) == False


def test_CheckFact():
    calc = Calculate()

    # Passing different outputs
    assert calc.CalcFact(2) == 2
    assert calc.CalcFact(3) == 6
    # assert calc.CalcFact(0) == 0 # Supposed to throw an error

Ausgang:

============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items

test_a.py
[100%]

=============================================================== 2 passed in 0.04s ===============================================================

Nun, mit einem fehlgeschlagenen Testfall:

============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items

test_a.py .F
[100%]

=================================================================== FAILURES ====================================================================
________________________________________________________________ test_CheckFact _________________________________________________________________

    def test_CheckFact():
        calc = Calculate()

        # Passing different outputs
        assert calc.CalcFact(2) == 2
        assert calc.CalcFact(3) == 6
>       assert calc.CalcFact(0) == 0 # Supposed to throw an error

test_a.py:50: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test_a.py:13: in CalcFact
    return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
    return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
    return a * self.CalcFact(a-1)
.
.
.
.
.
RecursionError: maximum recursion depth exceeded in comparison

test_a.py:10: RecursionError
============================================================ short test summary info ============================================================ 
FAILED test_a.py::test_CheckFact - RecursionError: maximum recursion depth exceeded in comparison
========================================================== 1 failed, 1 passed in 2.42s ========================================================== 

Die mit Pytest geschriebenen Testfälle sind etwas einfacher als unittest; Anstatt eine Klasse zu erstellen, die das Kind von unittest.TestCase war, können wir unsere Testfunktionen einfach mit test_ am Anfang der Methode schreiben.

Vor- und Nachteile des Pytest-Frameworks

Im Folgenden sind einige Vorteile der Verwendung des Pytest-Frameworks in Python aufgeführt.

  • Kompakte Testsuiten

  • Minimaler Boilerplate-Code

  • Plugin-Unterstützung

  • Saubere und korrekte Ausgabepräsentation

    Es hat auch einen Nachteil, der unten aufgeführt ist.

  • Oft inkompatibel mit anderen Frameworks

Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

Verwandter Artikel - Python Unit Test