Skip to content

13. Pytest

BohdanObruch edited this page Apr 18, 2024 · 2 revisions

Данный раздел разделен на несколько частей(каждый пункт - это ссылка на соответствующий раздел):

Переход по внутренним ссылкам происходит только при открытом разделе блока в котором находится ссылка.



Pytest - это фреймворк для написания тестов на языке Python. Он позволяет писать простые и сложные тесты, а также управлять ими. Pytest позволяет писать тесты в стиле BDD, а также позволяет использовать маркировку тестов, фикстуры и параметризацию.

Установка Pytest

Для большинства пакетов 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 # выводит только ошибки и отчёт о тестах

Примеры использования аргументов:

--co

Нажать, чтобы раскрыть
pytest --co

Если нужно вывести все тесты в определённом файле, то нужно указать путь к файлу(или название файла) после --co:

pytest --co tests/test_example.py

-k

Нажать, чтобы раскрыть
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.

-m

Нажать, чтобы раскрыть
pytest -m slow

Команда равноценна

pytest -m "slow"

Если необходимо запустить несколько марок, то можно использовать логические операторы and, or, not:

pytest -m "slow or fast"

Командной выше будут запущены все тесты, где есть марка slow или fast.

--markers

Нажать, чтобы раскрыть
pytest --markers

--fixtures

Нажать, чтобы раскрыть
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 - это фикстура, которая предоставляет временный каталог, уникальный для вызова теста, созданный в базовом временном каталоге.

--setup-plan

Нажать, чтобы раскрыть
pytest --setup-plan

Выполнение команды pytest --setup-plan позволяет увидеть план запуска тестов в проекте.

Если нужно увидеть план запуска тестов определенного файла, то нужно указать название файла и ключ --setup-plan:

pytest test_simple.py --setup-plan  

--durations

Нажать, чтобы раскрыть
pytest test_simple.py --durations=2

Выполнение команды pytest test_simple.py --durations=2 позволяет увидеть два самых долгих теста в файле с тестами.

Таким образом, можно увидеть, какие тесты выполняются дольше всего, и оптимизировать их.

-v

Нажать, чтобы раскрыть
pytest -v

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

-s

Нажать, чтобы раскрыть
pytest -s

Выполнение команды pytest -s позволяет увидеть вывод тестов в реальном времени.

-l (--showlocals)

Нажать, чтобы раскрыть
pytest -l

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

-rfEX

Нажать, чтобы раскрыть
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

  • Марка 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

  • Марка skipif позволяет пропустить тест, если выполняется определенное условие. С помощью аргумента condition указывается условие, а с помощью аргумента reason - причина пропуска;
@pytest.mark.skipif(condition=True, reason="Этот тест еще не завершен")

На примере выше, тест будет пропущен, если условие condition=True выполняется, а именно если функция is_macos возвращает True. Если функция возвращает False, то тест будет запущен.

xfail

  • Марка 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

  • Марка 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

Переопределение названия параметра в ids

@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.param

@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.

Indirect параметризация

@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. Это позволяет запускать тесты только с определенными параметрами и использовать более красивые и читабельные декораторы.

__repr__ в параметризации

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

Содержание
Командная строка
Кодировка UTF-8 в Java
Список полезных книг для автоматизаторов тестирования на языке Java
Список полезных книг для автоматизаторов тестирования на языке Python Структура проекта Github README.md

Java:
1. Вводное занятие. Сразу к практике.
2. Git. GitHub. Погружаемся.
3. Погружаемся в инструментарий и библиотеки
4. Основы Java
5. Продолжаем разрабатывать автотесты. PageObjects
6. JUnit 5
7. Allure Reports
8. Работа с файлами
9. Selenide #1
10. Jenkins. Создаем первую задачу
11. Управляем параметрами в коде и в Jenkins
12. Отправляем уведомления о результатах прохождения автотестов
13. Учимся быстро разрабатывать проекты для тестовых заданий
14. Selenoid
15. Библиотека Owner
16. REST API. Пишем автотесты с Rest assured
17. REST API. Декомпозируем UI тесты. Подключаем отчетность
18. REST API. Продолжаем изучать
19. Мобильная автоматизация #1. Разрабатываем автотесты с Browserstack
20. Allure TestOps
21. Переезд на собственную инфраструктуру Java

Python:
1. Вводное занятие. Сразу к практике!
2. Погружаемся в инструментарий и библиотеки
3. Git. GitHub. Погружаемся.
4. Основы Python
5. Selene #1
6. Основы Python. Часть II
7. Основы Python. Часть III
8. Page Object
9. Allure Reports
10. Работаем с файлами Python
11. Jenkins. Создаем первую задачу и управляем параметрами Python
12. Телеграм бот. Отправляем уведомления о результатах прохождения тестов
13. Pytest
14. Selenoid
15. Pytest. Часть II
16. Venv, Poetry и управление зависимостями проекта
17. REST API. Часть I. Пишем автотесты с Requests
18. REST API. Часть II. Продолжаем изучать
19. Мобильная автоматизация #1. Разрабатываем автотесты с Browserstack
20. Мобильная автоматизация #2. Разрабатываем автотесты с эмулятором Android устройства
21. Allure TestOps
22. Переезд на собственную инфраструктуру Python

Clone this wiki locally