Pytest для начинающих

Автоматизированное тестирование является неотъемлемой частью процесса разработки.

Хотя на первый взгляд может показаться, что написание тестов продлевает процесс разработки, в долгосрочной перспективе это экономит вам массу времени.

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

В этой статье мы рассмотрим, как использовать pytest, чтобы вы могли самостоятельно использовать его для улучшения процесса разработки и следовать более продвинутым руководствам по pytest.

Содержимое

Цели

К концу этой статьи вы сможете:

  1. Объясните, что такое pytest и как вы можете его использовать
  2. Напишите тест с помощью pytest самостоятельно
  3. Следуйте более сложным инструкциям, в которых используется pytest
  4. Подготовьте данные и/или файлы, необходимые для тестирования
  5. Настройте параметры теста
  6. Имитируйте функциональность, необходимую для теста

Зачем нужен pytest

Несмотря на то, что тестирование часто упускается из виду, оно настолько важно, что Python поставляется со своей собственной встроенной платформой тестирования под названием unittest. Однако написание тестов в unittest может быть сложным, поэтому в последние годы фреймворк pytest стал стандартом.

Вот некоторые существенные преимущества pytest:

  1. требуется меньше шаблонного кода, что делает ваши наборы тестов более удобочитаемыми
  2. использует простой оператор assert, а не методы assertSomething от unittest (например, assertEquals, assertTrue)
  3. система fixture упрощает настройку и отмену режима тестирования
  4. функциональный подход
  5. обширная экосистема плагинов, поддерживаемая сообществом

Начало работы

Поскольку это скорее руководство, чем самоучитель, мы подготовили простое приложение FastAPI, на которое вы можете ссылаться при чтении этой статьи. Вы можете клонировать его с GitHub.

В базовой ветви наш API имеет 4 конечные точки (определенные в main.py ), которые используют функции из calculations.py возвращает результат выполнения некоторой базовой арифметической операции (+/-/*//) над двумя целыми числами. В ветке advanced_topics добавлены еще две функции:

  1. CalculationsStoreJSON ( внутри store_calculations.py ) класса - позволяет сохранять и извлекать вычисления в/из файла JSON.
  2. get_number_fact ( внутри number_facts.py ) - выполняет вызов удаленного API для получения информации об определенном числе.

Для понимания этой статьи не требуется никаких знаний о FastAPI.

В первой части этой статьи мы будем использовать раздел основы.

Создайте и активируйте виртуальную среду и установите требования:

$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$ pip install -r requirements.txt

Организация и присвоение имен

Для организации ваших тестов вы можете использовать три возможности, все из которых используются в примере проекта:

Организовано в Пример
Пакет Python (папка, содержащая файл __init__.py) "test_calculations"
Модуль test_commutative_operations.py
Класс TestCalculationEndpoints

Когда дело доходит до рекомендаций по организации тестов, у каждого программиста есть свои предпочтения.

Цель этой статьи - не показать лучшие практики, а, напротив, показать вам все возможности.

pytest самостоятельно обнаружит тесты, если вы будете соблюдать следующие соглашения:

  • вы добавляете свои тесты в файл, который начинается с test_ или заканчивается на _test.py (например, test_foo.py или foo_test.py)
  • вы добавляете к тестовым функциям префикс test_ (например, def test_foo())
  • если вы используете классы, вы добавляете свои тесты в качестве методов в класс с префиксом Test (например, class TestFoo)

Тесты, не соответствующие соглашению об именовании, найдены не будут, поэтому будьте осторожны с присвоением имен.

Стоит отметить, что соглашение об именовании может быть изменено в командной строке или в конфигурационном файле).

Тест по анатомии

Давайте посмотрим, как выглядит тестовая функция test_return_sum (в файле test_calculation_endpoints.py ):

# tests/test_endpoints/test_calculation_endpoints.py


def test_return_sum(self):
   # Arrange
   test_data = {
      "first_val": 10,
      "second_val": 8
   }
   client = TestClient(app)

   # Act
   response = client.post("/sum/", json=test_data)

   # Assert
   assert response.status_code == 200
   assert response.json() == 18

Каждая тестовая функция, согласно документации pytest , состоит из четырех этапов:

  1. Организовать - где вы готовите все для своего теста (test_data = {"first_val": 10, "second_val": 8})
  2. Действие - единичное действие, изменяющее состояние, которое запускает поведение, которое вы хотите протестировать. (client.post("/sum/", json=test_data))
  3. Утверждать - сравнивает результат действия с желаемым результатом (assert response.json() == 18)
  4. Очистка - очистка данных, относящихся к конкретному тесту (обычно в тестах, которые тестируют более сложные функции, вы можете увидеть пример в наших советах)

Запуск тестов

pytest дает вам широкий контроль над тем, какие тесты вы хотите запустить:

  1. все тесты
  2. конкретный пакет
  3. конкретный модуль
  4. конкретный класс
  5. конкретный тест
  6. тесты, соответствующие определенному ключевому слову

Давайте посмотрим, как это работает...

Если вы придерживаетесь нашего примера приложения, pytest уже установлено, если вы установили требования.

Для ваших собственных проектов pytest можно установить как любой другой пакет с pip:

(venv)$ pip install pytest

Запуск всех тестов

Выполнение команды pytest просто запустит все тесты, которые сможет найти pytest:

(venv)$ python -m pytest

=============================== test session starts ===============================
platform darwin -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/pytest_for_beginners_test_project
plugins: anyio-3.6.1
collected 8 items

tests/test_calculations/test_anticommutative_operations.py ..               [ 25%]
tests/test_calculations/test_commutative_operations.py ..                   [ 50%]
tests/test_endpoints/test_calculation_endpoints.py ....                     [100%]

================================ 8 passed in 5.19s ================================

pytest сообщит вам, сколько тестов найдено и в каких модулях они были найдены. В нашем примере приложения pytest нашел 8 тестов, и все они прошли проверку.

В нижней части сообщения вы можете увидеть, сколько тестов пройдено/не выполнено.

Неправильный шаблон именования

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

Например, если вы переименуете класс TestCalculationEndpoints в CalculationEndpointsTest, все тесты внутри него просто не будут выполняться:

=============================== test session starts ===============================
platform darwin -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/pytest_for_beginners_test_project
plugins: anyio-3.6.1
collected 4 items

tests/test_calculations/test_anticommutative_operations.py ..               [ 50%]
tests/test_calculations/test_commutative_operations.py ..                   [100%]

================================ 4 passed in 0.15s ================================

Измените название обратно на TestCalculationEndpoints, прежде чем двигаться дальше.

Неудачный тест

Ваш тест не всегда будет пройден с первой попытки.

Измените прогнозируемый результат в инструкции assert в инструкции test_calculate_sum, чтобы увидеть, как выглядит результат неудачного теста:

# tests/test_calculations/test_commutative_operations.py


def test_calculate_sum():

    calculation = calculate_sum(5, 3)

    assert calculation == 7 # whops, a mistake

Запустите тест. Вы должны увидеть что-то похожее на:

=============================== test session starts ===============================
platform darwin -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/pytest_for_beginners_test_project
plugins: anyio-3.6.1
collected 8 items

tests/test_calculations/test_anticommutative_operations.py ..               [ 25%]
tests/test_calculations/test_commutative_operations.py F.                   [ 50%]
tests/test_endpoints/test_calculation_endpoints.py ....                     [100%]

==================================== FAILURES =====================================
_______________________________ test_calculate_sum ________________________________

    def test_calculate_sum():

        calculation = calculate_sum(5, 3)

>       assert calculation == 7
E       assert 8 == 7

tests/test_calculations/test_commutative_operations.py:8: AssertionError
============================= short test summary info =============================
FAILED tests/test_calculations/test_commutative_operations.py::test_calculate_sum
=========================== 1 failed, 7 passed in 0.26s ===========================

В нижней части сообщения вы можете увидеть раздел краткая информация о тестировании. Здесь указано, какой тест не прошел и где. В этом случае фактический результат - 8 - не соответствует ожидаемому -- 7.

Если вы прокрутите немного выше, то увидите подробный результат неудачного теста, так что будет легче определить, что пошло не так (полезно при проведении более сложных тестов).

Исправьте этот тест, прежде чем двигаться дальше.

Запуск тестов в определенном пакете или модуле

Чтобы запустить определенный пакет или модуль, вам просто нужно добавить полный относительный путь к определенному набору тестов в команду pytest.

Для посылки:

(venv)$ python -m pytest tests/test_calculations

Эта команда запустит все тесты внутри пакета "tests/test_calculations".

Для модуля:

(venv)$ python -m pytest tests/test_calculations/test_commutative_operations.py

Эта команда запустит все тесты внутри модуля tests/test_calculations/test_commutative_operations.py .

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

Выполнение тестов в определенном классе

Чтобы получить доступ к определенному классу в pytest, вам нужно написать относительный путь к его модулю, а затем добавить класс после :::

(venv)$ python -m pytest tests/test_endpoints/test_calculation_endpoints.py::TestCalculationEndpoints

Эта команда выполнит все тесты внутри класса TestCalculationEndpoints.

Запуск определенного теста

Вы можете получить доступ к определенному тесту так же, как и к классу, с двумя двоеточиями после относительного пути, за которыми следует имя теста:

(venv)$ python -m pytest tests/test_calculations/test_commutative_operations.py::test_calculate_sum

Если функция, которую вы хотите запустить, находится внутри класса, необходимо выполнить один тест в следующей форме:

relative_path_to_module::TestClass::test_method

Например:

(venv)$ python -m pytest tests/test_endpoints/test_calculation_endpoints.py::TestCalculationEndpoints::test_return_sum

Запуск тестов по ключевому слову

Теперь предположим, что вы хотите запускать только тесты, связанные с делением. Поскольку мы включили слово "разделенный" в название теста для тестов, связанных с делением, вы можете запускать только эти тесты следующим образом:

(venv)$ python -m pytest -k "dividend"

Итак, будут выполнены 2 из 8 тестов:

=============================== test session starts ===============================
platform darwin -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/pytest_for_beginners_test_project
plugins: anyio-3.6.1
collected 8 items / 6 deselected / 2 selected

tests/test_calculations/test_anticommutative_operations.py .                [ 50%]
tests/test_endpoints/test_calculation_endpoints.py .                        [100%]

========================= 2 passed, 6 deselected in 0.18s =========================

Это не единственные способы выбора определенного набора тестов. Обратитесь к официальной документации для получения дополнительной информации.

Флаги тестирования, которые стоит запомнить

pytest включает в себя множество флагов; вы можете перечислить их все с помощью команды pytest --help.

К числу наиболее полезных относятся:

  1. pytest -v увеличивает детализацию на один уровень, а pytest -vv увеличивает ее на два уровня. Например, при использовании параметризации (многократном запуске одного и того же теста с разными входами/выходами), запуск только pytest информирует вас о том, сколько версий теста пройдено и сколько не пройдено, в то время как добавление -v также выводит, какие параметры были использованы. Если вы добавите -vv, вы увидите каждую тестовую версию с входными параметрами. Более подробный пример вы можете увидеть в документации pytest.
  2. pytest --lf повторно запускаются только те тесты, которые не были выполнены во время последнего запуска. Если сбоев нет, будут выполнены все тесты.
  3. Добавление флага -x приводит к немедленному завершению работы pytest при первой ошибке или неудачном тестировании.

Настройка параметров

Мы рассмотрели основы и теперь переходим к более сложным темам.

Если вы согласны с репозиторием, переключите ветку с основы на расширенные темы (git checkout advanced_topics).

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

Вы можете добавить несколько входов и соответствующих им выходов с помощью настройки с помощью @pytest.mark.parametrize декоратора.

Например, при антикоммутативных операциях важен порядок передаваемых чисел. Было бы разумно охватить больше случаев, чтобы гарантировать, что функция работает корректно во всех случаях:

# tests/test_calculations/test_anticommutative_operations.py


import pytest

from calculations import calculate_difference


@pytest.mark.parametrize(
    "first_value, second_value, expected_output",
    [
        (10, 8, 2),
        (8, 10, -2),
        (-10, -8, -2),
        (-8, -10, 2),
    ]
)
def test_calculate_difference(first_value, second_value, expected_output):

    calculation = calculate_difference(first_value, second_value)

    assert calculation == expected_output

@pytest.mark.parametrize имеет строго структурированную форму:

  1. Вы передаете декоратору два аргумента:
    1. Строка с именами параметров, разделенными запятыми
    2. Список значений параметров, положение которых соответствует положению имен параметров
  2. Вы передаете имена параметров в тестовую функцию (они не зависят от позиции)

Если вы запустите этот тест, он будет выполняться 4 раза, каждый раз с разными входными и выходными данными:

(venv)$ python -m pytest -v  tests/test_calculations/test_anticommutative_operations.py::test_calculate_difference

=============================== test session starts ===============================
platform darwin -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /Users/michael/repos/testdriven/pytest_for_beginners_test_project
plugins: anyio-3.6.1
collected 4 items

tests/test_calculations/test_anticommutative_operations.py::test_calculate_difference[10-8-2] PASSED [ 25%]
tests/test_calculations/test_anticommutative_operations.py::test_calculate_difference[8-10--2] PASSED [ 50%]
tests/test_calculations/test_anticommutative_operations.py::test_calculate_difference[-10--8--2] PASSED [ 75%]
tests/test_calculations/test_anticommutative_operations.py::test_calculate_difference[-8--10-2] PASSED [100%]

================================ 4 passed in 0.01s ================================

Фикстуры

Рекомендуется перенести этап Упорядочение (и, следовательно, Очистка) в отдельный этап функция fixture работает, когда шаг Arrange выполняется в точности одинаково в нескольких тестах или если он настолько сложен, что ухудшает читаемость тестов.

Создание

Функция помечается как фиксированная с помощью @pytest.fixture декоратора.

В старой версии TestCalculationEndpoints был шаг для создания TestClient в каждом методе.

Например:

# tests/test_endpoints/test_calculation_endpoints.py


def test_return_sum(self):
    test_data = {
        "first_val": 10,
        "second_val": 8
    }
    client = TestClient(app)

    response = client.post("/sum/", json=test_data)

    assert response.status_code == 200
    assert response.json() == 18

В ветке advanced_topics вы увидите, что метод теперь выглядит намного чище:

# tests/test_endpoints/test_calculation_endpoints.py


def test_return_sum(self, test_app):
    test_data = {
        "first_val": 10,
        "second_val": 8
    }

    response = test_app.post("/sum/", json=test_data)

    assert response.status_code == 200
    assert response.json() == 18

Вторые два были оставлены без изменений, так что вы можете сравнить их (не делайте этого в реальной жизни, в этом нет смысла).

test_return_sum теперь используется приспособление под названием test_app, которое вы можете увидеть в файле conftest.py:

# tests/conftest.py


import pytest
from starlette.testclient import TestClient

from main import app


@pytest.fixture(scope="module")
def test_app():
    client = TestClient(app)

    return client

Что происходит?

  1. Декоратор @pytest.fixture() помечает функцию test_app как фикстуру. Когда pytest считывает этот модуль, он добавляет эту функцию в список фикстур. Затем тестовые функции могут использовать любой инструмент из этого списка.
  2. Этот инструмент представляет собой простую функцию, которая возвращает TestClient, поэтому можно выполнять тестовые вызовы API.
  3. Аргументы тестовой функции сравниваются со списком параметров. Если значение аргумента совпадает с именем параметра, то параметр будет разрешен, а его возвращаемое значение будет записано в качестве аргумента в тестовой функции.
  4. Тестовая функция использует результат тестирования прибора таким же образом, как и любое другое значение переменной.

Еще одна важная вещь, на которую следует обратить внимание, заключается в том, что функции передается не сам фиксатор, а его значение.

Область применения

Фикстуры создаются при первом запросе теста, но уничтожаются в зависимости от их области применения. После того, как fixture будет уничтожен, его нужно будет вызвать снова, если этого потребует другой тест; таким образом, вам нужно помнить о том, что fixtures требует больших затрат времени (например, вызовы API).

Существует пять возможных областей, от самой узкой до самой широкой:

Scope Description
function (default) The fixture is destroyed at the end of the test.
class The fixture is destroyed during the teardown of the last test in the class.
module The fixture is destroyed during the teardown of the last test in the module.
package The fixture is destroyed during the teardown of the last test in the package.
session The fixture is destroyed at the end of the test session.

Чтобы изменить область действия в предыдущем примере, вам просто нужно задать параметр scope:

# tests/conftest.py


import pytest
from starlette.testclient import TestClient

from main import app


@pytest.fixture(scope="function") # scope changed
def test_app():
    client = TestClient(app)

    return client

Насколько важно определить минимально возможную область применения, зависит от того, насколько трудоемким является устройство. Создание TestClient не отнимает много времени, поэтому изменение области действия не сокращает время выполнения теста. Но, например, выполнение 10 тестов с использованием устройства, вызывающего внешний API, может занять очень много времени, поэтому, вероятно, лучше всего использовать область module.

Временные файлы

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

Чтобы избежать взаимодействия между несколькими тестовыми файлами или даже с остальной частью приложения и процессом дополнительной очистки, лучше всего использовать уникальный временный каталог.

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

Тестируемый код можно найти в store_calculations.py:

# store_calculations.py


import json


class CalculationsStoreJSON:
    def __init__(self, json_file_path):
        self.json_file_path = json_file_path
        with open(self.json_file_path / "calculations.json", "w") as file:
            json.dump([], file)

    def add(self, calculation):
        with open(self.json_file_path/"calculations.json", "r+") as file:
            calculations = json.load(file)
            calculations.append(calculation)
            file.seek(0)
            json.dump(calculations, file)

    def list_operation_usages(self, operation):
        with open(self.json_file_path / "calculations.json", "r") as file:
            calculations = json.load(file)

        return [calculation for calculation in calculations if calculation['operation'] == operation]

Обратите внимание, что при инициализации CalculationsStoreJSON вы должны указать json_file_path, где будет храниться ваш JSON-файл. Это может быть любой допустимый путь на диске; вы передаете его таким же образом для рабочего кода и тестов.

К счастью, pytest предоставляет ряд встроенных инструментов, один из которых мы можем использовать в данном случае под названием tmppath:

# tests/test_advanced/test_calculations_storage.py


from store_calculations import CalculationsStoreJSON

def test_correct_calculations_listed_from_json(tmp_path):
    store = CalculationsStoreJSON(tmp_path)
    calculation_with_multiplication = {"value_1": 2, "value_2": 4, "operation": "multiplication"}

    store.add(calculation_with_multiplication)

    assert store.list_operation_usages("multiplication") == [{"value_1": 2, "value_2": 4, "operation": "multiplication"}]

Этот тест проверяет, можем ли мы при сохранении вычисления в JSON-файл с использованием метода CalculationsStoreJSON.add() получить список определенных операций с помощью UserStoreJSON.list_operation_usages().

Мы передали в этот тест параметр tmp_path, который возвращает объект path (pathlib.Path), указывающий на временный каталог внутри базового каталога.

При использовании tmp_path, pytest создает:

  1. базовый временный каталог
  2. временный каталог (внутри базового каталога), уникальный для каждого вызова тестовой функции

Стоит отметить, что для облегчения отладки pytest создает новый базовый временный каталог во время каждого сеанса тестирования, в то время как старые базовые каталоги удаляются после 3 сеансов.

Исправление ошибок

С помощью monkeypatching вы динамически изменяете поведение фрагмента кода во время выполнения, фактически не изменяя исходный код.

Хотя это не обязательно ограничивается только тестированием, в pytest это используется для изменения поведения части кода внутри тестируемого модуля. Обычно он используется для замены дорогостоящих вызовов функций, таких как HTTP-вызовы API, некоторым заранее определенным фиктивным поведением, которым быстро и легко управлять.

Например, вместо вызова реального API для получения ответа вы возвращаете некоторый жестко запрограммированный ответ, который используется внутри тестов.

Давайте посмотрим глубже. В нашем приложении есть функция, которая возвращает факт о некотором числе, полученном из общедоступного API:

# number_facts.py


import requests

def get_number_fact(number):
    url = f"http://numbersapi.com/{number}?json"
    response = requests.get(url)
    json_resp = response.json()

    if json_resp["found"]:
        return json_resp["text"]

    return "No fact about this number."

Вы не хотите вызывать API во время ваших тестов, потому что:

  • он работает медленно
  • он подвержен ошибкам (возможно, API не работает, возможно, у вас плохое подключение к Интернету, ...)

В этом случае вы хотите имитировать ответ, чтобы он возвращал интересующую нас часть без фактического выполнения HTTP-запроса:

# tests/test_advanced/test_number_facts.py


import requests

from number_facts import get_number_fact


class MockedResponse:

    def __init__(self, json_body):
        self.json_body = json_body

    def json(self):
        return self.json_body


def mock_get(*args, **kwargs):
    return MockedResponse({
        "text": "7 is the number of days in a week.",
        "found": "true",
    })


def test_get_number_fact(monkeypatch):
    monkeypatch.setattr(requests, 'get', mock_get)

    number = 7
    fact = '7 is the number of days in a week.'

    assert get_number_fact(number) == fact

Здесь многое происходит:

  1. в тестовой функции используется встроенный в pytest инструмент monkeypatch.
  2. Используя monkeypatch.setattr, мы заменили функцию get пакета requests нашей собственной функцией mock_get. Все вызовы внутри кода приложения для requests.get теперь фактически будут вызывать mock_get во время выполнения этого теста.
  3. Функция mock_get возвращает экземпляр MockedResponse, который заменяет json_body значением, которое мы присвоили внутри функции mock_get ({'"text": "7 is the number of days in a week.", "found": "true",}).
  4. При каждом запуске этого теста вместо выполнения requests.get("http://numbersapi.com/7?json"), как в производственном коде (get_number_fact), будет возвращен MockedResponse с жестко заданным фактом.

Таким образом, вы все еще можете проверить поведение своей функции (получить информацию о числе из ответа API), не вызывая API на самом деле.

Заключение

Существует ряд причин, по которым pytest стал стандартом за последние несколько лет, в первую очередь:

  1. Это упрощает написание тестов.
  2. Благодаря своим всеобъемлющим выводам можно легко определить, какие тесты завершились неудачей и почему.
  3. Программа предоставляет решения для подготовки повторяющихся или сложных тестов, создания файлов для тестирования и изоляции тестов.

pytest предлагает гораздо больше, чем то, что мы рассмотрели в этой статье.

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

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

Вот некоторые из них, которые могут оказаться полезными:

  • pytest-cov добавляет поддержку для проверки покрытия кода.
  • pytest-django добавляет набор ценных инструментов для тестирования приложений Django.
  • pytest-xdist позволяет выполнять тесты параллельно, тем самым сокращая время, необходимое для выполнения тестов.
  • pytest-случайным образом запускает тесты в случайном порядке, предотвращая их случайную зависимость друг от друга.
  • pytest-asincio упрощает тестирование асинхронных программ.
  • pytest-mock предоставляет модуль mocker, который является оболочкой стандартного пакета unittest mock, а также дополнительные утилиты.

Эта статья должна была помочь вам понять, как работает библиотека pytest и чего с ее помощью можно достичь. Однако понимание того, как работает pytest, и как работает тестирование - это не одно и то же. Обучение написанию осмысленных тестов требует практики и понимания того, что вы ожидаете от своего кода.

Back to Top