Тестовые классы и методы
Помимо написания функций тестирования, Pytest позволяет использовать классы. Как уже упоминалось, нет необходимости в наследовании, и тестовые классы следуют нескольким простым правилам. Использование классов обеспечивает большую гибкость и возможность повторного использования. Как вы видите далее, Pytest держится вне пути и избегает принудительного написания тестов определенным образом.
Как и с функциями, вы по-прежнему можете писать утверждения с помощью утверждения assert.
Создание тестового класса
Давайте рассмотрим реальный сценарий, чтобы узнать, как могут помочь тестовые классы. Следующая функция проверяет, содержит ли указанный файл "да" в его содержимом. Если да, возвращается True. Если файл не существует или содержит "нет" в его содержимом, он возвращается False. Этот сценарий распространен в асинхронных задачах, использующих файловую систему для указания завершения.
Вот как выглядит функция:
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
Теперь вот как класс с двумя тестами (по одному для каждого условия) в файле с именем 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
Внимание
Методы теста используют путь /tmp для временного тестового файла, так как его проще использовать. Однако если вам нужно использовать временные файлы, рассмотрите возможность безопасного создания (и удаления) библиотеки типа tempfile. Не каждая система имеет каталог /tmp , и это расположение может не быть временным в зависимости от операционной системы.
Выполнение тестов с флагом -v для повышения детализации показывает, что тесты проходят:
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 ===============================
Хотя тесты передаются, они выглядят повторяющимися, и они также покидают файлы после завершения теста. Прежде чем мы узнаем, как улучшить их, рассмотрим вспомогательные методы в следующем разделе.
Вспомогательные методы
В тестовом классе существует несколько методов, которые можно использовать для настройки и разрыва выполнения теста. Pytest выполняет их автоматически, если они определены. Чтобы использовать эти методы, необходимо знать, что они имеют определенный порядок и поведение.
-
setup: выполняется один раз перед каждым тестом в классе. -
teardown: выполняется один раз после каждого теста в классе. -
setup_class: выполняется один раз перед всеми тестами в классе. -
teardown_class: выполняется один раз после всех тестов в классе.
Если для работы тестов требуются аналогичные (или идентичные) ресурсы, полезно написать методы установки. В идеале тест не должен покидать ресурсы после завершения, поэтому методы очистки могут помочь в удалении тестов в таких ситуациях.
Очистка
Давайте рассмотрим обновленный тестовый класс, который очищает файлы после каждого теста:
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
Так как мы использовали teardown() этот метод, этот тестовый класс больше не оставляет за собой /tmp/test_file .
Настройка
Еще одно улучшение, которое мы можем сделать для этого класса, заключается в добавлении переменной, указывающей на файл. Так как файл теперь объявлен в шести местах, любые изменения в пути будут означать го изменение во всех этих местах. В этом примере показано, как выглядит класс с добавленным setup() методом, объявляющим переменную пути:
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
Пользовательские вспомогательные методы
Пользовательские вспомогательные методы можно создавать в классе. Эти методы не должны быть префиксированы именем test и не могут быть названы как методы установки или очистки. В классе TestIsDone мы могли бы автоматизировать создание временного файла с помощью пользовательского вспомогательного средства. Этот настраиваемый вспомогательный метод может выглядеть следующим образом:
def write_tmp_file(self, content):
with open(self.tmp_file, "w") as _f:
_f.write(content)
Pytest не выполняет write_tmp_file() метод автоматически, а другие методы могут вызывать его непосредственно для сохранения повторяющихся задач, таких как запись в файл.
Весь класс выглядит следующим образом, после обновления методов тестирования для использования пользовательского вспомогательного средства:
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
Когда следует использовать класс вместо функции
Не существует строгих правил относительно того, когда следует использовать класс вместо функции. Всегда рекомендуется следовать соглашениям в текущих проектах и командах, с которыми вы работаете. Ниже приведены некоторые общие вопросы, которые помогут вам определить, когда следует использовать класс:
- Требуется ли ваш тест аналогичный вспомогательный код установки или очистки?
- Группирование тестов вместе имеет логический смысл?
- Есть ли хотя бы несколько тестов в наборе тестов?
- Могут ли тесты воспользоваться общим набором вспомогательных функций?