13. Pytest - qa-guru/knowledge-base GitHub Wiki
Данный раздел разделен на несколько частей(каждый пункт - это ссылка на соответствующий раздел):
Переход по внутренним ссылкам происходит только при открытом разделе блока в котором находится ссылка.
- Установка Pytest
- Аргументы запуска
- Задаем по умолчанию для всех тестов определенные параметры
- Марки
- Как навесить марку на весь файл с тестами
- Как запустить тесты несколько раз
- Как добавить свои марки
- Параметризация
- Параметризация фикстур
- Indirect параметризация
- Присваивание фикстуры в переменную
- repr в параметризации
Pytest - это фреймворк для написания тестов на языке Python. Он позволяет писать простые и сложные тесты, а также управлять ими. Pytest позволяет писать тесты в стиле BDD, а также позволяет использовать маркировку тестов, фикстуры и параметризацию.
Для большинства пакетов Python, установка доступна с помощью команды pip. Для установки pytest, выполните следующую команду:
python -m pip install pytestУвидеть все команды для pytest можно с помощью команды pytest --help
pytest --co (pytest --collect-only) # позволяет вывести все доступные тесты в директории/проекте
pytest -k "вхождение строки любого теста" # запуск конкретного теста
pytest -m "марка теста" # запуск тестов с определённой маркой
pytest --markers # вывод всех доступных марок в проекте
pytest --fixtures # выводит список всех доступных фикстур
pytest --durations=x # вывод x самых долгих тестов
pytest -l (pytest --showlocals) # выводит локальные переменные в тестах
pytest --setup-plan # тесты не запускает, но выводит план запуска тестов
pytest -v (pytest --verbose) # выводит более подробную информацию о тестах
pytest -s # выводит вывод тестов в реальном времени
pytest -rfEX # выводит только ошибки и отчёт о тестахПримеры использования аргументов:
Нажать, чтобы раскрыть
pytest --co
Если нужно вывести все тесты в определённом файле, то нужно указать путь к файлу(или название файла) после --co:
pytest --co tests/test_example.pyНажать, чтобы раскрыть
pytest --co -k mobileГде слово mobile - это часть названия теста, которое мы хотим увидеть в проекте. В данном случае, мы увидим все тесты, в названии которых есть слово mobile.

Если нужно запустить все тесты, где есть слово mobile, то нужно использовать команду:
pytest -k mobile
Если необходимо запустить несколько тестов, с разными словами в названии, то можно использовать логические операторы and, or, not:
pytest -k "mobile or desktop"Командной выше будут запущены все тесты, где есть слово mobile или desktop.

Если необходимо указать более сложное условие, то можно использовать скобки:
pytest -k "(homework and mobile) or test_first"Командной выше будут запущены все тесты, где есть слово homework и mobile, а также тест с названием test_first.

Нажать, чтобы раскрыть
pytest -m slow
Команда равноценна
pytest -m "slow"Если необходимо запустить несколько марок, то можно использовать логические операторы and, or, not:
pytest -m "slow or fast"Командной выше будут запущены все тесты, где есть марка slow или fast.

Нажать, чтобы раскрыть
pytest --markers
Нажать, чтобы раскрыть
pytest --fixtures
При запуске команды pytest --fixtures выводятся все доступные фикстуры в проекте.
Если фикстура имеет параметры, то они также отображаются.
cache - это фикстура, которая используется для кеширования результатов тестов. Если тесты выполняются медленно, то можно использовать эту фикстуру для ускорения выполнения тестов.
capsysbinary - это фикстура, которая используется для перехвата вывода тестов, используя использует бинарный вывод.
capfd - это фикстура, которая используется для перехвата вывода тестов.
capfdbinary - это фикстура, которая используется для перехвата вывода тестов на более низком уровне, используя бинарный вывод.
capsys - это фикстура, которая используется для перехвата вывода тестов.
doctest_namespace - это фикстура, которая используется для передачи пространства имен в doctest.
pytestconfig - это фикстура, которая используется для доступа к конфигурации pytest.
record_property - это фикстура, которая используется для записи свойств тестов.
record_xml_attribute - это фикстура, которая используется для записи атрибутов XML.
record_testsuite_property - это фикстура, которая используется для записи свойств тестов.
tmpdir_factory - это сессионная фикстура, которая может использоваться для создания произвольных временных каталогов из любой другой фикстуры или теста.
tmpdir - это фикстура, которая используется для создания временных каталогов.
caplog - это фикстура, которая используется для перехвата вывода тестов.
monkeypatch - это фикстура, которая используется для изменения поведения тестов.
recwarn - это фикстура, которая используется для записи предупреждений тестов.
tmp_path_factory - это сессионная фикстура, которая предоставляет временный каталог, уникальный для вызова теста, созданный в базовом временном каталоге.
tmp_path - это фикстура, которая предоставляет временный каталог, уникальный для вызова теста, созданный в базовом временном каталоге.
Нажать, чтобы раскрыть
pytest --setup-planВыполнение команды pytest --setup-plan позволяет увидеть план запуска тестов в проекте.

Если нужно увидеть план запуска тестов определенного файла, то нужно указать название файла и ключ --setup-plan:
pytest test_simple.py --setup-plan 
Нажать, чтобы раскрыть
pytest test_simple.py --durations=2Выполнение команды pytest test_simple.py --durations=2 позволяет увидеть два самых долгих теста в файле с тестами.

Таким образом, можно увидеть, какие тесты выполняются дольше всего, и оптимизировать их.
Нажать, чтобы раскрыть
pytest -vВыполнение команды pytest -v позволяет увидеть более подробную информацию о тестах.

Нажать, чтобы раскрыть
pytest -sВыполнение команды pytest -s позволяет увидеть вывод тестов в реальном времени.
Нажать, чтобы раскрыть
pytest -lВыполнение команды pytest -l позволяет увидеть локальные переменные в тестах.

Нажать, чтобы раскрыть
pytest -rfEXВыполнение команды pytest -rfEX позволяет увидеть только ошибки и отчет о тестах. А именно failed, expected, xfailed.

Для этого в файле pytest.ini нужно добавить следующие строки:
[pytest]
addopts = -v -l --durations=10Или в файле pyproject.toml если используется poetry в проекте:
[tool.pytest.ini_options]
addopts = "-v -l --durations=10"Теперь при запуске тестов, будут использоваться параметры -v -l --durations=10 по умолчанию.
Марки позволяют маркировать тесты, разделять их на группы, управлять отдельными тестами и группами.
Краткий список марок:
- Марка
skipпозволяет пропустить тест и не выполнять его содержимое. С помощью аргументаreasonследует указывать причину пропуска. - Марка
skipможет быть использована для пропуска тестов, которые еще не готовы к запуску. Или для пропуска тестов, которые не нужно запускать в данный момент.
@pytest.mark.skip()
@pytest.mark.skip(reason="Этот тест еще не завершен")
Также можно делать пропуск теста, если он соответствует определенному условию:
is_macos = True
@pytest.fixture()
def is_macos():
return True
def test_third(is_macos):
if is_macos:
pytest.skip(reason="Не запускается на macos")- Марка
skipifпозволяет пропустить тест, если выполняется определенное условие. С помощью аргументаconditionуказывается условие, а с помощью аргументаreason- причина пропуска;
@pytest.mark.skipif(condition=True, reason="Этот тест еще не завершен")
На примере выше, тест будет пропущен, если условие condition=True выполняется, а именно если функция is_macos возвращает True. Если функция возвращает False, то тест будет запущен.
- Марка
xfailпозволяет указать, что тест заведомо может не работать. При этом тест запустится. Причину также указывают с помощью аргументаreason.
@pytest.mark.xfail()
@pytest.mark.xfail(reason="просто потому что")
def test_fail():
user1 = random.randint(0, 100)
user2 = random.randint(0, 100)
assert user1 <= 100
assert user2 <= 100
try:
assert user1 == user2
except AssertionError:
pytest.xfail("TASK-1234")
Если тест прошел успешно(исправили к примеру баг), то он будет отмечен как XPASS, если тест упал, то он будет отмечен как XFAIL.
Если на тест навешена марка xfail, и он будет запущен, но он не проверит ошибки синтаксиса внутри теста. То есть, если внутри теста есть ошибки, то они не будут показаны.
Чтобы подобного избежать, можно не использовать марку xfail, а использовать try и except:
def test_fail():
user1 = random.randint(0, 100)
user2 = random.randint(0, 100)
assert user1 <= 100
assert user2 <= 100
try:
assert user1 == user2
except AssertionError:
pytest.xfail("TASK-1234")И если в тесте есть ошибки синтаксиса, то они будут показаны.
- Марка
usefixturesпозволяет указать, какие фикстуры использовать для теста. С помощью аргументаfixturesуказывают, какие фикстуры использовать;
@pytest.mark.usefixtures("fixture_name")
Если необходимо навесить марку на весь файл с тестами, то можно использовать следующий код:
import pytest
pytestmark = pytest.mark.skip(reason="TASK-1234 Тест нестабильный потому что время от времени не хватает таймаута")
Для запуска тестов несколько раз, можно использовать плагинpytest-repeat. Для этого нужно установить пакет:
pip install pytest-repeatЕсли необходимо запускать все тесты несколько раз, то можно добавить в файл pytest.ini следующие строки:
[pytest]
addopts = --count=3
Теперь все тесты будут запускаться три раза.
Если необходимо запустить только определенный тест несколько раз, то можно использовать следующую команду:
pytest --count=3 test_simple.py
Выполнение команды pytest --count=3 test_simple.py позволяет запустить тесты определенное количество раз(В данном случае 3 раза).
Если вы создаете свои марки, то их пояснение нужно добавить в файл pytest.ini:
[pytest]
markers =
smoke: smoke tests
regression: regression tests
e2e: end-to-end testsИли в файл pyproject.toml если используется poetry в проекте:
[tool.pytest.ini_options]
markers=[
"fast: Маркируем тесты длящиеся менее пяти секунд",
"slow: Тесты, которые длятся больше пяти секунд"
]Это необходимо для того, чтобы при запуске тестов с марками, pytest понимал, что такие марки существуют. И что они означают.
Параметризация это способ протестировать один и тот же тест с разными входными данными.
Параметризация в pytest осуществляется с помощью декоратора @pytest.mark.parametrize.
@pytest.mark.parametrize("browser", ["Chrome", "Firefox", "Safari"])
def test_with_matrix_param(browser):
pass
@pytest.mark.parametrize("browser, version",
[("Chrome", 122.0), ("Firefox", 123.0), ("Safari", 12.3)]
)
def test_with_matrix_param(browser, version):
pass
@pytest.mark.parametrize("browser", ["Chrome", "Firefox", "Safari"])
@pytest.mark.parametrize("test_role", ["manager", "guest", "admin"])
def test_with_matrix_param(browser, test_role):
pass
@pytest.mark.parametrize("browser, version",
[("Chrome", "12-rc4125"), ("Firefox", 123.0), ("Safari", 12.3)],
ids=["Chrome", "Firefox", "Safari"]
)
def test_with_matrix_param(browser, version):
pass
Таким образом мы получаем название как часть имени теста. И мы можем запускать тесты по названию.
pytest -k "Chrome"@pytest.mark.parametrize("browser",
[
pytest.param("Chrome", id="Chrome"),
pytest.param("Firefox", marks=[pytest.mark.slow]),
pytest.param("Safari", marks=[pytest.mark.xfail(reason="TASK-123 Safari problem")]),
]
)
def test_with_param_marks(browser):
passВ данной функции тестирования, мы используем параметризацию с помощью pytest.param.
pytest.param позволяет добавить маркировку к параметрам.

@pytest.fixture(params=["Chrome", "Firefox", "Safari"])
def browser(request):
if request.param == "Chrome":
return ""
if request.param == "Firefox":
return ""
if request.param == "Safari":
return ""
def test_with_parametrized_fixture(browser):
passВ данном примере, мы параметризуем фикстуру browser. request.param - это параметр, который мы передаем в фикстуру. А из фикстуры мы получаем значение, через request.

@pytest.fixture(params=["Chrome", "Firefox", "Safari"])
def browser(request):
if request.param == "Chrome":
return ""
if request.param == "Firefox":
return ""
if request.param == "Safari":
return ""
@pytest.mark.parametrize("browser", ["Chrome"], indirect=True) # переопределяем фикстуру, чтобы запускалось только с Chrome
def test_with_indirect_parametrization(browser):
passВ данном примере, мы используем параметризацию фикстуры с помощью indirect=True. Это позволяет передать параметры из фикстуры в тест.
Индиректная параметризация позволяет переопределить фикстуру, чтобы запускалось только с определенным параметром.

@pytest.fixture(params=["Chrome", "Firefox", "Safari"])
def browser(request):
if request.param == "Chrome":
return ""
if request.param == "Firefox":
return ""
if request.param == "Safari":
return ""
chrome_only = pytest.mark.parametrize("browser", ["Chrome"], indirect=True)
@chrome_only
def test_chrome_extension(browser):
passВ данном примере, мы присваиваем фикстуру в переменную chrome_only. И используем ее в декораторе @chrome_only.
Это позволяет запускать тесты только с определенными параметрами и использовать более красивые и читабельные декораторы.

@dataclass
class User:
id: int
name: str
age: int
description: str
def __repr__(self):
return f"{self.name} ({self.id})"
user1 = User(id=1, name="Mario", age=32, description="something " * 10)
user2 = User(id=2, name="Wario", age=62, description="else " * 10)
def show_user(user):
return f"{user.name} ({user.id})"
@pytest.mark.parametrize("user", [user1, user2], ids=show_user)
def test_users(user):
passВ данном примере, мы используем параметризацию с помощью дата класса User. Используем функцию show_user для отображения имени и id пользователя в названии теста. И передаем ее в параметр ids.
Также метод __repr__ в датаклассе, позволяет получить строковое представление объекта в виде имени и id. И потом использовать его в ids.

Смотрите только когда не можете понять что делать(нажать, чтобы раскрыть)
Задание
1. Пропустите мобильный тест, если соотношение сторон десктопное (и наоборот);
2. Переопределите параметр с помощью indirect;
3. Сделайте разные фикстуры для каждого теста.
Первое задание
Согласно 1 заданию нужно создать фикстуру, которая будет пропускать мобильный тест, если соотношение сторон десктопное (и наоборот).
# conftest.py
@pytest.fixture(params=[(набор размеров экранов)])
def setup_browser(request):
width, height = request.param
browser.config.window_width = width
browser.config.window_height = height
if width > 900:
yield "desktop"
else:
yield "mobile"
browser.quit()
# test_github_skip.py
def test_mobile_skip(setup_browser):
if setup_browser == "mobile":
pytest.skip("Это мобилное разрешение")
browser.open("https://github.com/")
...
def test_desktop_skip(setup_browser):
if setup_browser == "desktop":
pytest.skip("Это десктопное разрешение")
browser.open("https://github.com/")
...Второе задание
Согласно 2 заданию нужно переопределить параметр с помощью indirect.
# conftest.py
@pytest.fixture(params=[(набор размеров экранов)])
def desktop_browser(request):
width, height = request.param
browser.config.window_width = width
browser.config.window_height = height
yield
browser.quit()
@pytest.fixture(params=[(набор размеров экранов)])
def mobile_browser(request):
width, height = request.param
browser.config.window_width = width
browser.config.window_height = height
yield
browser.quit()
# test_github_indirect.py
@pytest.mark.parametrize("desktop_browser", [(набор размеров экранов)], indirect=True)
def test_desktop_indirect(desktop_browser):
browser.open("https://github.com/")
...
@pytest.mark.parametrize("mobile_browser", [(набор размеров экранов)], indirect=True)
def test_mobile_indirect(mobile_browser):
browser.open("https://github.com/")
...Третье задание
Согласно 3 заданию нужно создать разные фикстуры для каждого теста.# conftest.py
@pytest.fixture(params=[(набор размеров экранов)])
def desktop_browser(request):
width, height = request.param
browser.config.window_width = width
browser.config.window_height = height
yield
browser.quit()
@pytest.fixture(params=[(набор размеров экранов)])
def mobile_browser(request):
width, height = request.param
browser.config.window_width = width
browser.config.window_height = height
yield
browser.quit()
# test_github_fixture.py
def test_desktop_fixture(desktop_browser):
browser.open("https://github.com/")
...
def test_mobile_fixture(mobile_browser):
browser.open("https://github.com/")
...