Klasy testowe i metody
Oprócz pisania funkcji testowych narzędzie Pytest umożliwia korzystanie z klas. Jak już wspomniano, nie ma potrzeby dziedziczenia, a klasy testowe są zgodne z kilkoma prostymi regułami. Korzystanie z klas zapewnia większą elastyczność i możliwość ponownego użycia. Jak widać dalej, Pytest trzyma się z drogi i unika zmuszania do pisania testów w określony sposób.
Podobnie jak funkcje, nadal można zapisywać asercji przy użyciu instrukcji assert
.
Tworzenie klasy testowej
Użyjmy rzeczywistego scenariusza, aby zobaczyć, jak klasy testowe mogą pomóc. Poniższa funkcja sprawdza, czy dany plik zawiera wartość "yes" w jego zawartości. Jeśli tak, zwraca wartość True
. Jeśli plik nie istnieje lub jeśli zawiera wartość "nie" w jego zawartości, zwraca wartość False
. Ten scenariusz jest typowy w zadaniach asynchronicznych, które używają systemu plików do wskazywania ukończenia.
Oto jak wygląda funkcja:
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
Oto jak wygląda klasa z dwoma testami (po jednym dla każdego warunku) w pliku o nazwie 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
Uwaga
Metody testowe używają ścieżki /tmp dla tymczasowego pliku testowego, ponieważ jest to łatwiejsze do użycia w tym przykładzie. Jeśli jednak musisz używać plików tymczasowych, rozważ użycie biblioteki takiej jak tempfile
ta, która może bezpiecznie je utworzyć (i usunąć). Nie każdy system ma katalog /tmp i ta lokalizacja może nie być tymczasowa w zależności od systemu operacyjnego.
Uruchomienie testów z flagą -v
w celu zwiększenia szczegółowości pokazuje wyniki testów:
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 ===============================
Mimo że testy przechodzą, wyglądają powtarzalnie i pozostawiają również pliki po zakończeniu testu. Zanim zobaczymy, jak możemy je ulepszyć, omówimy metody pomocnika w następnej sekcji.
Metody pomocnika
W klasie testowej istnieje kilka metod, których można użyć do konfigurowania i usuwania wykonania testu. Narzędzie Pytest wykonuje je automatycznie, jeśli są zdefiniowane. Aby użyć tych metod, należy wiedzieć, że mają one określoną kolejność i zachowanie.
setup
: wykonuje raz przed każdym testem w klasieteardown
: wykonuje raz po każdym teście w klasiesetup_class
: wykonuje raz przed wszystkimi testami w klasieteardown_class
: wykonuje raz po wszystkich testach w klasie
Gdy testy wymagają pracy podobnych (lub identycznych) zasobów, warto napisać metody konfiguracji. W idealnym przypadku test nie powinien pozostawiać zasobów po zakończeniu, więc metody usuwania danych mogą pomóc w czyszczeniu testów w tych sytuacjach.
Czyszczenie
Przyjrzyjmy się zaktualizowanej klasie testowej, która czyści pliki po każdym teście:
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
Ponieważ użyliśmy teardown()
metody, ta klasa testowa nie pozostawia już /tmp/test_file .
Ustawienia
Kolejną poprawą, którą możemy wprowadzić w tej klasie, jest dodanie zmiennej wskazującej plik. Ponieważ plik jest teraz zadeklarowany w sześciu miejscach, wszelkie zmiany w ścieżce oznaczałyby zmianę go we wszystkich tych miejscach. W tym przykładzie pokazano, jak klasa wygląda z dodaną setup()
metodą, która deklaruje zmienną ścieżki:
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
Niestandardowe metody pomocnika
Niestandardowe metody pomocnicze można tworzyć w klasie. Te metody nie mogą być poprzedzone nazwą test
i nie mogą być nazwane jako metody instalacji ani oczyszczania. TestIsDone
W klasie możemy zautomatyzować tworzenie pliku tymczasowego w niestandardowym pomocniku. Ta niestandardowa metoda pomocnika może wyglądać następująco:
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
Narzędzie Pytest nie wykonuje write_tmp_file()
automatycznie metody, a inne metody mogą wywołać ją bezpośrednio, aby zapisać je w powtarzających się zadaniach, takich jak zapisywanie w pliku.
Cała klasa wygląda jak w tym przykładzie po zaktualizowaniu metod testowych w celu użycia niestandardowego pomocnika:
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
Kiedy należy używać klasy zamiast funkcji
Nie ma żadnych rygorystycznych reguł dotyczących tego, kiedy należy używać klasy zamiast funkcji. Zawsze dobrym pomysłem jest przestrzeganie konwencji w bieżących projektach i zespołach, z którymi pracujesz. Poniżej przedstawiono kilka ogólnych pytań, które mogą pomóc w ustaleniu, kiedy należy użyć klasy:
- Czy testy wymagają podobnej konfiguracji lub kodu pomocnika oczyszczania?
- Czy grupowanie testów ma sens logiczny?
- Czy w zestawie testów znajduje się co najmniej kilka testów?
- Czy testy mogą korzystać ze wspólnego zestawu funkcji pomocnika?