Syntax highlighting of c5400ae ~( python/pytest)
= Pytest = <<TableOfContents()>> pytest - фреймворк для тестирования приложений == Полезные ссылки == [[https://docs.pytest.org/en/latest/|Homepage|class=" moin-https"]] [[https://docs.pytest.org/en/latest/reference/plugin_list.html|Plugin List|class=" moin-https"]] == Полезные команды и опции == {{{#!highlight bash # --- запуск тестов из заданного файла или файлов pytest tests/test_one.py test/test_two.py # --- запуск тестов из заданной директории pytest tests/foo/bar # --- запуск конкретных методов pytest tests/test_one.py::test_foo # --- выводить подробную информацию pytest -v pytest --verbose # --- не выводить трейс pytest -tb=no # --- выводить последовательность отработки тестов и фикстур pytest --setup-show # --- включаем стандартный вывод из тестовых функций pytest -s # --- строгие маркеры: все используемые маркеры должны быть описаны в pytest.ini pytest --strict-markers # --- отобразить причину, почему тест не прошел (fail, error, skip, xfail, xpass) pytest -ra }}} == Конфигурация == {{{#!highlight ini # pytest.ini [pytest] testpaths = tests pythonpath = . markers = smoke: subset of tests addopts = --strict-markers --strict-config -ra }}} == Написание тестов == === assert === {{{#!highlight python assert something assert not something assert a == b assert a != b assert a is None assert is not None assert a <= b }}} === pytest.fail() === Если по какой-то причине не получается применить `assert`, то можно использовать `pytest.fail()` {{{#!highlight python import pytest def test_with_fail(): # ... if c1 != c2: pytest.fail("thet don't match") }}} === Исключения === Проверяем исключение {{{#!highlight python def test_exception(): with pytest.raises(TypeError): foo.bar() }}} Проверяем исключение и соответствие сообщения в исключении заданному шаблону {{{#!highlight python def test_raises_with_info(): match_regex = 'missing 1 .* positional arguments' with pytest.raises(TypeError, match=metch_regex): foo.bar() }}} == Фикстуры == Примитивная фикстура {{{#!highlight python import pytest @pytest.fixture() def some_data(): return 42 def test_some_data(some_data): assert some_data == 42 }}} === Fixture Scope === {{{#!highlight python # --- фикстура уровня модуля @pytest.fixture(scope="module") def some_data(): return 42 }}} `scope='function` - запускается перед каждой тестовой функцией. Значение по умолчанию. `scope='class'` - запускается один раз перед тестовым классом. `scope='module'` - запускается один раз перед всеми тестами модуля. `scope='session'` - запускается только один раз. === conftest.py === Общий фикстуры для файлов в директории размещаем в файле ''conftest.py'' {{{#!highlight python # tests/foo/conftest.py @pytest.fixture(scope='session') def some_data(): return 42 # tests/foo/test_one.py def test_foo_one(some_date): assert some_data == 42 # tests/foo/test_two.py def test_foo_two(some_date): assert some_data == 42 }}} === Автоиспользование === Фикстура может быть запущена автоматом, если добавить аргумент `autouse=True` {{{#!highlight python @pytest.fixture(autouse=True, scope='session'): def footer_session_scope(): '''Показываем время в конце сессии''' yield now = time.time() print('--') print(time.strftime('%d %b %X', time.localtime(now))) }}} === Переименование фикстур === Случается так, что хочется иметь фикстуру, но имя уже занято. Ситуацию можно исправить с помощью аргумента `name` {{{#!highlight python import pytest from foo import app @pytest.fixture(scope='session', name='app') def _app(): yield app() def test_uses_app(app): assert app.bar == 'bar' }}} === Встроенные фикстуры === ''tmp_path'' - возвращает ''pathlib.Path'', который указывает на временную директорию (function scope). ''tmp_path_factory'' - возвращает объект TempPathFactory (session scope). См. [[https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html|How to use temporary directories and files in tests|class=" moin-https"]] ''capsys'' - управление вводом-выводом из тестов. См. [[https://docs.pytest.org/en/7.1.x/how-to/capture-stdout-stderr.html|How to capture stdout/stderr output|class=" moin-https"]]. ''monkeypatch'' - модификация (мутация) объекта или окружения. См. [[https://docs.pytest.org/en/7.1.x/how-to/monkeypatch.html|How to monkeypatch/mock modules and environments|class=" moin-https"]] == Параметризация == {{{#!highlight python @pytest.mark.parametrize( 'a, b, c', [(1, 2, 3), (2, 3, 5)] ) def test_sum(a, b, c): assert a + b == c }}} Существуют другие способы: параметризация фикстур, `pytest_generate_tests`. == Маркеры == === Встроенные маркеры === ''@pytest.mark.skip(reason=None)'' - пропустить тест с произвольной прочиний {{{#!highlight python @pytest.mark.skip(reason="Some reason") def test_lest_than(): assert foo() < bar() }}} ''@pytest.mark.skipif(confition, ...)'' - пропустить тест при некотором условии {{{#!highlight python @pytest.mark.skipif( parse(somemodule.__version__).major < 2, reason='version not supported' ) def test_less_than(): assert foo() < bar() }}} ''@pytest.mark.xfail(condition, ...)'' - этот тест ожидаемо падает. `pytest` посветит желтым, но завершит работу с 0. Хорошая альтернатива комментированию. {{{#!highlight python @pytest.mark.xfail(reason='test 123') def test_foo(): assert 1 == 0 }}} === Кастомные маркеры === Примеры маркеров {{{#!highlight python @pytest.mark.smoke }}} Описываем свои маркеры в ''pytest.ini'', чтобы `pytest` не сыпал предупреждения: {{{#!highlight python # pytest.ini # ... markers = smoke: subset of tests }}} Вызов тестов, помеченных маркером ''smoke'' {{{#!highlight bash pytest -m smoke }}} Маркер уровня файла {{{#!highlight python import pytest pytestmark = pytest.mark.finish }}} Маркер уровня класса {{{#!highlight python @pytest.mark.smoke class TestFinish: def test_foo(): assert True }}} Маркер уровня параметров: {{{#!highlight python @pytest.mark.parametrize( "start_state", [ "todo", pytest.param("in prog", marks=pytest.mark.smoke), "done", ], ) def test_finish(start_state): assert foo(start_state) }}} Несколько маркеров для теста {{{#!highlight python @pytest.mark.smoke @pytest.mark.exception def test_foo(): assert True }}} Комбинирование маркеров при вызове `pytest` {{{#!highlight bash pytest -m "finish and exception" pytest -m "finish and not exception" pytest -m "(exception or smoke) and (not finish)" }}} == Изоляция == Mocking an Attribute {{{#!highlight python from unittest import mock # ... def test_mock_version(): with mock.patch.object(cards, "__version__", "1.2.3"): result = runner.invoke(app, ["version"]) assert result.stdout.rstrip() == "1.2.3" }}} Mocking a Class and Methods {{{#!highlight python @pytest.fixture() def mock_cardsdb(): with mock.patch.object(cards, "CardsDB", autospec=True) as CardsDB: yield CardsDB.return_value def test_mock_path(mock_cardsdb): mock_cardsdb.path.return_value = '/foo/' result = runner.invoke(app, ['config']) assert result.stdout.rstrip() == '/foo/' }}}
