Тестовые классы и методы
Помимо написания функций тестирования, 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
Когда следует использовать класс вместо функции
Не существует строгих правил относительно того, когда следует использовать класс вместо функции. Всегда рекомендуется следовать соглашениям в текущих проектах и командах, с которыми вы работаете. Ниже приведены некоторые общие вопросы, которые помогут вам определить, когда следует использовать класс:
- Требуется ли ваш тест аналогичный вспомогательный код установки или очистки?
- Группирование тестов вместе имеет логический смысл?
- Есть ли хотя бы несколько тестов в наборе тестов?
- Могут ли тесты воспользоваться общим набором вспомогательных функций?