Python · Тестирование с pytest · Средний
Маркеры и выбор тестов
Помечайте тесты встроенными и пользовательскими маркерами для управления запуском.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Метки на практике
#Краткий справочник по меткам
#Упражнения:
Пропуск теста с указанием причины
#Напишите тест-функцию `test_future_feature`, которая проверяет ещё не реализованную функциональность. Пометьте её `@pytest.mark.skip` с описательной строкой `reason`. Запустите `pytest -v` и убедитесь, что тест показывается как `SKIPPED` с видимой причиной. Затем запустите `pytest -v -rs`, чтобы увидеть причину пропуска в разделе итогов.
import pytest
@pytest.mark.skip(reason='...') # укажите вашу причину здесь
def test_future_feature():
# этот код никогда не выполняется при пропуске
result = some_unimplemented_function()
assert result == 42
Решение
import pytest
@pytest.mark.skip(reason='payment refunds not implemented yet -- see issue #42')
def test_future_feature():
result = some_unimplemented_function()
assert result == 42
Пропуск на основе версии Python
#Напишите тест, использующий `@pytest.mark.skipif` для пропуска при версии Python ниже 3.10. Внутри тела теста используйте синтаксис оператора `match` (добавленного в Python 3.10): `match value: case 1: result = "one"`. Запустите `pytest -v` -- тест должен выполняться на Python 3.10+ и пропускаться на более ранних версиях. Выведите `sys.version` в тесте, чтобы подтвердить, какая версия его запустила.
import sys
import pytest
@pytest.mark.skipif(
# условие: sys.version_info < (?, ?),
# reason='...',
)
def test_match_statement():
value = 1
# используйте здесь оператор match/case
# assert result == 'one'
Решение
import sys
import pytest
@pytest.mark.skipif(
sys.version_info < (3, 10),
reason='match statement added in Python 3.10',
)
def test_match_statement():
print(f'running on Python {sys.version}')
value = 1
match value:
case 1:
result = 'one'
case _:
result = 'other'
assert result == 'one'
Пометить тест как ожидаемо падающий (xfail)
#Напишите два теста: `test_known_bug` и `test_known_bug_strict`. Оба вызывают `int("abc")` без try/except (это выбрасывает `ValueError`). Пометьте `test_known_bug` через `@pytest.mark.xfail`, а `test_known_bug_strict` через `@pytest.mark.xfail(strict=True)`. Запустите `pytest -v` -- оба должны показываться как `XFAIL`. Обратите внимание на разницу: без `strict` неожиданное прохождение показывается как `XPASS` (предупреждение, не ошибка); с `strict=True` неожиданное прохождение становится жёстким `FAILED`, блокирующим CI.
import pytest
@pytest.mark.xfail(reason=...) # укажите причину
def test_known_bug():
result = int('abc')
assert result == 0
@pytest.mark.xfail(strict=..., reason=...) # добавьте параметр strict=
def test_known_bug_strict():
result = int('abc')
assert result == 0
Решение
import pytest
@pytest.mark.xfail(reason='bug #77: int() does not handle letters')
def test_known_bug():
result = int('abc')
assert result == 0
# С strict=True: если тест неожиданно пройдёт, pytest сообщит об FAILED
@pytest.mark.xfail(strict=True, reason='bug #77')
def test_known_bug_strict():
result = int('abc')
assert result == 0
Регистрация пользовательской метки и фильтрация через -m
#Добавьте `slow` в секцию `markers` в `pytest.ini`. Напишите три тест-функции в одном файле: `test_fast_calculation` (без метки, просто `assert 2 + 2 == 4`), `test_slow_api_call` и `test_slow_db_query` (обе помечены `@pytest.mark.slow`). Запустите `pytest -m slow -v` и убедитесь, что выполняются только два медленных теста. Запустите `pytest -m "not slow" -v` и убедитесь, что выполняется только быстрый тест.
# pytest.ini
# [pytest]
# markers =
# slow: marks tests as slow (deselect with '-m "not slow"')
# test_mixed.py
import pytest
def test_fast_calculation():
assert 2 + 2 == 4
@pytest.mark.slow
def test_slow_api_call():
# имитация медленной операции
import time
time.sleep(0.1)
assert True
@pytest.mark.slow
def test_slow_db_query():
import time
time.sleep(0.1)
assert True
Решение
# pytest.ini
# [pytest]
# markers =
# slow: marks tests as slow (deselect with '-m "not slow"')
# test_mixed.py
import pytest
def test_fast_calculation():
assert 2 + 2 == 4
@pytest.mark.slow
def test_slow_api_call():
import time
time.sleep(0.1)
assert True
@pytest.mark.slow
def test_slow_db_query():
import time
time.sleep(0.1)
assert True
Комбинирование нескольких меток
#Зарегистрируйте метки `slow` и `unix_only` в `pytest.ini`. Напишите `test_fast_check` без меток. Напишите `test_file_permissions`, помеченный `@pytest.mark.slow`, `@pytest.mark.unix_only` И `@pytest.mark.skipif(sys.platform == 'win32', ...)`. Запустите `pytest -v`, чтобы убедиться, что оба теста выполняются (или пропускаются на Windows). Затем запустите `pytest -m slow -v` -- выбирается только медленный тест. Затем `pytest -m unix_only -v` -- тот же результат. Обратите внимание: `@pytest.mark.skipif` обеспечивает проверку во время выполнения, а метка -- фильтрацию через CLI.
# pytest.ini
# [pytest]
# markers =
# slow: slow tests
# unix_only: runs only on Unix/Linux/macOS
import sys
import pytest
def test_fast_check():
assert 'py' in 'pytest'
@pytest.mark.slow
@pytest.mark.unix_only
@pytest.mark.skipif(sys.platform == 'win32', reason='uses POSIX file permissions')
def test_file_permissions():
import os
import tempfile
with tempfile.NamedTemporaryFile() as f:
os.chmod(f.name, 0o644)
mode = oct(os.stat(f.name).st_mode)[-3:]
assert mode == '644'
Решение
# pytest.ini
# [pytest]
# markers =
# slow: slow tests
# unix_only: runs only on Unix/Linux/macOS
import sys
import pytest
def test_fast_check():
assert 'py' in 'pytest'
@pytest.mark.slow
@pytest.mark.unix_only
@pytest.mark.skipif(sys.platform == 'win32', reason='uses POSIX file permissions')
def test_file_permissions():
import os
import tempfile
with tempfile.NamedTemporaryFile() as f:
os.chmod(f.name, 0o644)
mode = oct(os.stat(f.name).st_mode)[-3:]
assert mode == '644'