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/")
...