Testklasser og metoder
I tillegg til å skrive testfunksjoner, kan du bruke klasser i Pytest. Som vi allerede har nevnt, er det ikke behov for arv, og testklassene følger noen enkle regler. Bruk av klasser gir deg mer fleksibilitet og gjenbruk. Som du ser neste, Pytest holder seg ute av veien og unngår å tvinge deg til å skrive tester på en bestemt måte.
Akkurat som funksjoner, kan du fortsatt skrive deklarasjoner ved hjelp av assert-setningen.
Bygge en testklasse
La oss bruke et virkelig scenario for å se hvordan testklasser kan hjelpe. Følgende funksjon kontrollerer om en gitt fil inneholder «ja» i innholdet. I så fall returneres True. Hvis filen ikke finnes, eller hvis den inneholder nei i innholdet, returneres False. Dette scenarioet er vanlig i asynkrone oppgaver som bruker filsystemet til å angi fullføring.
Slik ser funksjonen 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
Her ser du hvordan en klasse med to tester (én for hver betingelse) i en fil med navnet test_files.py ser 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
Forsiktighet
Testmetodene bruker /tmp-banen for en midlertidig testfil fordi det er enklere å bruke for eksempelet. Hvis du imidlertid trenger å bruke midlertidige filer, kan du vurdere å bruke et bibliotek som tempfile som kan opprette (og fjerne) dem trygt for deg. Ikke alle systemer har en /tmp-katalog , og denne plasseringen er kanskje ikke midlertidig avhengig av operativsystemet.
Hvis du kjører testene med -v flagg for å øke detaljnivået, vises testene som passerer:
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 ===============================
Selv om testene består, ser de repeterende ut, og de forlater også filer etter at testen er fullført. Før vi ser hvordan vi kan forbedre dem, kan vi dekke hjelpemetoder i neste del.
Hjelpemetoder
I en testklasse finnes det noen metoder du kan bruke til å konfigurere og rive ned testkjøring. Pytest utfører dem automatisk hvis de er definert. Hvis du vil bruke disse metodene, bør du vite at de har en bestemt rekkefølge og virkemåte.
-
setup: Utfører én gang før hver test i en klasse. -
teardown: Utføres én gang etter hver test i en klasse. -
setup_class: Utføres én gang før alle testene i en klasse. -
teardown_class: Utfører én gang etter alle testene i en klasse.
Når tester krever lignende (eller identiske) ressurser for å fungere, er det nyttig å skrive konfigurasjonsmetoder. Ideelt sett bør en test ikke etterlate ressurser når den er fullført, så teardown-metoder kan hjelpe til med å teste opprydding i slike situasjoner.
Opprydding
La oss se på en oppdatert testklasse som rydder opp i filene etter hver 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
Fordi vi brukte teardown() metoden, etterlater ikke denne testklassen lenger en /tmp/test_file bak.
Installasjonsprogrammet
En annen forbedring vi kan gjøre i denne klassen, er å legge til en variabel som peker til filen. Siden filen nå er deklarert på seks steder, vil eventuelle endringer i banen bety å endre den på alle disse stedene. Dette eksemplet viser hvordan klassen ser ut med en ekstra setup() metode som deklarerer banevariabelen:
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
Egendefinerte hjelpemetoder
Du kan opprette egendefinerte hjelpemetoder i en klasse. Disse metodene kan ikke prefikses med navnet test og kan ikke kalles konfigurasjons- eller oppryddingsmetoder.
TestIsDone I klassen kan vi automatisere oppretting av den midlertidige filen i en egendefinert hjelper. Denne egendefinerte hjelpemetoden kan se slik ut:
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
Pytest kjører ikke automatisk write_tmp_file() metoden, og andre metoder kan kalle det direkte for å lagre på gjentakende oppgaver som å skrive til en fil.
Hele klassen ser slik ut når du har oppdatert testmetodene for å bruke den egendefinerte hjelperen:
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 skal bruke en klasse i stedet for en funksjon
Det finnes ingen strenge regler for når du skal bruke en klasse i stedet for en funksjon. Det er alltid lurt å følge konvensjonene i gjeldende prosjekter og team som du arbeider med. Her er noen generelle spørsmål du kan stille som kan hjelpe deg med å avgjøre når du skal bruke en klasse:
- Trenger testene lignende kode for oppsett eller oppryddingshjelper?
- Gir gruppering av testene sammen logisk mening?
- Er det minst noen tester i testserien?
- Kan testene dra nytte av et felles sett med hjelpefunksjoner?