Testování tříd a metod
Kromě psaní testovacích funkcí umožňuje Pytest používat třídy. Jak jsme už zmínili, není nutné dědičnost a třídy testů dodržují několik jednoduchých pravidel. Použití tříd vám dává větší flexibilitu a možnost opakovaného použití. Jak vidíte dále, Pytest se vyhýbá cestě a vyhne se vynucení psaní testů určitým způsobem.
Stejně jako funkce můžete pomocí příkazu assert
pořád psát kontrolní výrazy.
Sestavení testovací třídy
Pojďme použít skutečný scénář k tomu, abychom viděli, jak můžou testovací třídy pomoct. Následující funkce zkontroluje, jestli daný soubor ve svém obsahu obsahuje "ano". Pokud ano, vrátí True
. Pokud soubor neexistuje nebo pokud v obsahu obsahuje "ne", vrátí False
hodnotu . Tento scénář je běžný v asynchronních úlohách, které používají systém souborů k označení dokončení.
Tady je postup, jak funkce vypadá:
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
Teď se dozvíte, jak vypadá třída se dvěma testy (jeden pro každou podmínku) v souboru s názvem test_files.py :
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
Upozornění
Testovací metody používají cestu /tmp pro dočasný testovací soubor, protože je pro tento příklad jednodušší. Pokud ale potřebujete používat dočasné soubory, zvažte použití knihovny, jako tempfile
je ta, která je může bezpečně vytvořit (a odebrat). Ne každý systém má adresář /tmp a toto umístění nemusí být dočasné v závislosti na operačním systému.
Spuštění testů s příznakem -v
pro zvýšení úrovně podrobností ukazuje úspěšné testy:
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 ===============================
I když testy procházejí, vypadají opakovaně a po dokončení testu také opouštějí soubory. Než se podíváme, jak je můžeme vylepšit, pojďme se podívat na pomocné metody v další části.
Pomocné metody
V testovací třídě existuje několik metod, které můžete použít k nastavení a odstranění spuštění testu. Pytest je spustí automaticky, pokud jsou definované. Pokud chcete tyto metody použít, měli byste vědět, že mají konkrétní pořadí a chování.
setup
: Provede se jednou před každým testem ve třídě.teardown
: Spustí se jednou po každém testu ve třídě.setup_class
: Provede se jednou před všemi testy ve třídě.teardown_class
: Spustí se jednou po všech testech ve třídě.
Pokud testy vyžadují, aby fungovaly podobné (nebo identické) prostředky, je užitečné psát metody nastavení. V ideálním případě by test neměl po dokončení opustit prostředky, takže metody pro odstranění kódu můžou v těchto situacích pomoct s vyčištěním testů.
Vyčištění
Pojďme se podívat na aktualizovanou testovací třídu, která po každém testu vyčistí soubory:
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
Vzhledem k tomu, že jsme použili metodu teardown()
, tato testovací třída již neopustí /tmp/test_file za sebou.
Nastavení
Dalším vylepšením, které můžeme v této třídě provést, je přidat proměnnou, která odkazuje na soubor. Vzhledem k tomu, že soubor je teď deklarován na šesti místech, všechny změny cesty by znamenaly změnu na všech těchto místech. Tento příklad ukazuje, jak třída vypadá s přidanou setup()
metodou, která deklaruje proměnnou cesty:
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
Vlastní pomocné metody
Ve třídě můžete vytvořit vlastní pomocné metody. Tyto metody nesmí mít předponu názvu test
a nelze je pojmenovat jako metody nastavení nebo čištění. TestIsDone
Ve třídě bychom mohli automatizovat vytvoření dočasného souboru ve vlastním pomocníku. Tato vlastní pomocná metoda může vypadat jako v tomto příkladu:
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
Pytest metodu write_tmp_file()
nespustí automaticky a jiné metody ji můžou volat přímo, aby se uložily na opakované úlohy, jako je zápis do souboru.
Celá třída vypadá jako tento příklad po aktualizaci testovacích metod pro použití vlastní pomocné rutiny:
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
Kdy použít třídu místo funkce
Neexistují žádná striktní pravidla, kdy použít třídu místo funkce. Vždy je vhodné dodržovat konvence v aktuálních projektech a týmech, se kterými pracujete. Tady je několik obecných otázek, které vám můžou pomoct určit, kdy použít třídu:
- Vyžadují vaše testy podobné nastavení nebo pomocné kódy pro vyčištění?
- Dává seskupení testů logický smysl?
- Máte v sadě testů alespoň několik testů?
- Mohly by vaše testy těžit z běžné sady pomocných funkcí?