Testklasser och metoder
Förutom att skriva testfunktioner kan du använda klasser i Pytest. Som vi redan nämnt finns det inget behov av arv och testklasserna följer några enkla regler. Med hjälp av klasser får du mer flexibilitet och återanvändning. Som du ser härnäst håller Pytest sig ur vägen och undviker att tvinga dig att skriva tester på ett visst sätt.
Precis som med funktioner kan du fortfarande skriva försäkran med hjälp av -instruktionen assert
.
Skapa en testklass
Nu ska vi använda ett verkligt scenario för att se hur testklasser kan hjälpa. Följande funktion kontrollerar om en viss fil innehåller "ja" i innehållet. I så fall returnerar True
den . Om filen inte finns eller om den innehåller "nej" i innehållet returneras False
. Det här scenariot är vanligt i asynkrona uppgifter som använder filsystemet för att indikera slutförande.
Så här ser funktionen ut:
import os
def is_done(path):
if not os.path.exists(path):
return False
with open(path) as _f:
contents = _f.read()
if "yes" in contents.lower():
return True
elif "no" in contents.lower():
return False
Så här ser en klass med två tester (en för varje villkor) i en fil med namnet test_files.py ut:
class TestIsDone:
def test_yes(self):
with open("/tmp/test_file", "w") as _f:
_f.write("yes")
assert is_done("/tmp/test_file") is True
def test_no(self):
with open("/tmp/test_file", "w") as _f:
_f.write("no")
assert is_done("/tmp/test_file") is False
Varning
Testmetoderna använder sökvägen /tmp för en tillfällig testfil eftersom den är enklare att använda för exemplet. Men om du behöver använda temporära filer bör du överväga att använda ett bibliotek som tempfile
det kan skapa (och ta bort) dem på ett säkert sätt åt dig. Alla system har inte en /tmp-katalog och den platsen kanske inte är tillfällig beroende på operativsystemet.
När du kör testerna -v
med flaggan för att öka verbositeten visas testerna som skickas:
pytest -v test_files.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
cachedir: .pytest_cache
rootdir: /private/
collected 2 items
test_files.py::TestIsDone::test_yes PASSED [ 50%]
test_files.py::TestIsDone::test_no PASSED [100%]
============================== 2 passed in 0.00s ===============================
Även om testerna skickas ser de repetitiva ut och de lämnar även filer när testet är klart. Innan vi ser hur vi kan förbättra dem ska vi gå igenom hjälpmetoderna i nästa avsnitt.
Hjälpmetoder
I en testklass finns det några metoder som du kan använda för att konfigurera och riva ned testkörning. Pytest kör dem automatiskt om de definieras. Om du vill använda dessa metoder bör du veta att de har en specifik ordning och beteende.
-
setup
: Körs en gång före varje test i en klass -
teardown
: Körs en gång efter varje test i en klass -
setup_class
: Körs en gång före alla tester i en klass -
teardown_class
: Körs en gång efter alla tester i en klass
När tester kräver liknande (eller identiska) resurser för att fungera är det användbart att skriva installationsmetoder. Helst bör ett test inte lämna resurser när det är klart, så metoder för nedbrytning kan hjälpa till vid testrensning i dessa situationer.
Rensa
Nu ska vi titta på en uppdaterad testklass som rensar filerna efter varje test:
class TestIsDone:
def teardown(self):
if os.path.exists("/tmp/test_file"):
os.remove("/tmp/test_file")
def test_yes(self):
with open("/tmp/test_file", "w") as _f:
_f.write("yes")
assert is_done("/tmp/test_file") is True
def test_no(self):
with open("/tmp/test_file", "w") as _f:
_f.write("no")
assert is_done("/tmp/test_file") is False
Eftersom vi använde teardown()
metoden lämnar den här testklassen inte längre en /tmp/test_file bakom sig.
Ställ in
En annan förbättring som vi kan göra i den här klassen är att lägga till en variabel som pekar på filen. Eftersom filen nu deklareras på sex platser innebär alla ändringar i sökvägen att den ändras på alla dessa platser. Det här exemplet visar hur klassen ser ut med en tillagd setup()
metod som deklarerar sökvägsvariabeln:
class TestIsDone:
def setup(self):
self.tmp_file = "/tmp/test_file"
def teardown(self):
if os.path.exists(self.tmp_file):
os.remove(self.tmp_file)
def test_yes(self):
with open(self.tmp_file, "w") as _f:
_f.write("yes")
assert is_done(self.tmp_file) is True
def test_no(self):
with open(self.tmp_file, "w") as _f:
_f.write("no")
assert is_done(self.tmp_file) is False
Anpassade hjälpmetoder
Du kan skapa anpassade hjälpmetoder i en klass. Dessa metoder får inte föregås av namnet test
och kan inte namnges som konfigurations- eller rensningsmetoder.
TestIsDone
I klassen kan vi automatisera skapandet av den tillfälliga filen i en anpassad hjälp. Den anpassade hjälpmetoden kan se ut som i det här exemplet:
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
Pytest kör write_tmp_file()
inte metoden automatiskt, och andra metoder kan anropa den direkt för att spara på repetitiva uppgifter som att skriva till en fil.
Hela klassen ser ut som i det här exemplet när testmetoderna har uppdaterats för att använda den anpassade hjälpen:
class TestIsDone:
def setup(self):
self.tmp_file = "/tmp/test_file"
def teardown(self):
if os.path.exists(self.tmp_file):
os.remove(self.tmp_file)
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
def test_yes(self):
self.write_tmp_file("yes")
assert is_done(self.tmp_file) is True
def test_no(self):
self.write_tmp_file("no")
assert is_done(self.tmp_file) is False
När du ska använda en klass i stället för en funktion
Det finns inga strikta regler för när du ska använda en klass i stället för en funktion. Det är alltid en bra idé att följa konventionerna i aktuella projekt och team som du arbetar med. Här följer några allmänna frågor som kan hjälpa dig att avgöra när du ska använda en klass:
- Behöver dina tester liknande konfigurations- eller rensningshjälpkod?
- Är det logiskt att gruppera dina tester tillsammans?
- Finns det åtminstone några tester i din testsvit?
- Kan dina tester dra nytta av en gemensam uppsättning hjälpfunktioner?