Примечание: это было первоначально опубликовано в martinheinz.dev
Тестирование вашего кода является неотъемлемой частью разработки и тесты качества могут сэкономить вам большую головную боль в по дороге. Есть много гидов на тестировании в Python и конкретно тестирование с Питиш , Но есть довольно много особенностей, которые я редко вижу упомянул где угодно, но мне часто нужен. Итак, здесь идет список трюков и особенности Pteest , что я не могу жить без (и ты тоже не сможешь – Скоро ).
Тестирование для исключения
Давайте начнем просто. У вас есть функция, которая бросает исключение, и вы хотите убедиться, что это происходит в правильном условиях и включает в себя правильное сообщение:
import pytest def test_my_function(): with pytest.raises(Exception, match='My Message') as e: my_function() assert e.type is ValueError
Здесь мы можем увидеть простой контекстный менеджер что Pteest обеспечивает нам. Это позволяет нам указывать тип исключения, который должен быть поднят, а также сообщение указанного исключения. Если исключение не поднимается в блоке, то тест не удается. Вы также можете проверить больше атрибутов исключения, поскольку контекстный менеджер возвращает ExceptionInfo
класс, который имеет такие атрибуты, как Тип
, ценить
или Traceback
Отказ
Фильтрация предупреждений
За исключением, давайте посмотрим на предупреждения. Иногда вы получаете кучу предупреждающих сообщений в ваших журналах изнутри библиотек, которые вы используете. Вы не можете исправить их, и они действительно просто создают ненужный шум, поэтому давайте избавимся от них:
import warnings from sqlalchemy.exc import SADeprecationWarning def test_my_function(): # SADeprecationWarning is issued by SQLAchemy when deprecated API is used warnings.filterwarnings("ignore", category=SADeprecationWarning) ... assert ... def test_my_function(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("ignore") # ignore annoying warnings ... assert ... # warnings restored
Здесь мы показываем 2 подхода – в первую очередь, мы прямо игнорируем все предупреждения о указанной категории, вставляя фильтр в передней части списка фильтров. Это приведет к тому, что ваша программа игнорирует все предупреждения этой категории, пока она не заканчивается, что может не всегда желательно. Со вторым подходом мы используем контекстный менеджер, который восстанавливает все предупреждения после выхода его объема. Мы также указываем запись = Правда
Так что мы можем проверить список выпущенных (игнорируемых) предупреждений, если это будет.
Тестирование стандартных выходных и стандартных сообщений об ошибках
Далее давайте посмотрим на следующий сценарий: у вас есть инструмент командной строки, который имеет кучу функций, которые печатают сообщения на стандартный выход, но не возвращайте ничего. Итак, как мы это проверим?
def test_my_function(capsys): my_function() # function that prints stuff captured = capsys.readouterr() # Capture output assert f"Received invalid message ..." in captured.out # Test stdout assert f"Fatal error ..." in captured.err # Test stderr
Решить это, Pteest Предоставляет крепеж под названием Капсы
, который – хорошо – захватывает вывод системы. Все, что вам нужно использовать, – это добавить его в качестве параметра в свою тестовую функцию. Далее, после вызова функции, которая проверяется, вы захватите выходы в форме кортежа – (OUT, ERR)
, который вы можете использовать в утверждении Assert.
Исправления предметов
Иногда при тестировании вам могут потребоваться заменить объекты, используемые в функциях, в соответствии с тестом, чтобы обеспечить более предсказуемый набор данных или избежать упомянутых функций доступа к ресурсам, которые могут быть недоступны. mock.patch
может помочь с этим:
from unittest import mock def test_my_function(): with mock.patch('module.some_function') as some_function: # Used as context manager my_function() # function that calls `some_function` some_function.assert_called_once() some_function.assert_called_with(2, 'x') @mock.patch('module.func') # Used as decorator def test_my_function(some_function): module.func(10) # Calls patched function some_function.assert_called_with(10) # True
В этом первом примере мы можем видеть, что можно исправлять функции, а затем проверять, сколько раз и с какими аргументами их называли. Эти патчи также могут быть сложены как в виде декоратора, так и контекстно-менеджера. Теперь для некоторых более мощных применений:
from unittest import mock def test_my_function(): with mock.patch.object(SomeClass, 'method_of_class', return_value=None) as mock_method: instance = SomeClass() instance.method_of_class('arg') mock_method.assert_called_with('arg') # True def test_my_function(): r = Mock() r.content = b'{"success": true}' with mock.patch('requests.get', return_value=r) as get: # Avoid doing actual GET request some_function() # Function that calls requests.get get.assert_called_once()
Первый пример в приведенном выше фрагменте довольно просты – мы заменяем метод SomeClass
и сделать его возвращением Нет
Отказ Во втором, более практическом примере мы избегаем зависящего от удаленного API/ресурса, заменяя requests.get
издевательства и сделав его возврат объекта, который мы поставляем с подходящими данными.
Есть еще много вещей, которые Макет
Модуль может сделать для вас, а некоторые из них довольно дикие, включая побочные эффекты, издевательства на свойства, издевательства несуществующих атрибутов и многое другое. Если вы столкнулись с проблемой при написании тестов, то вы должны определенно оформить заказ Документы Для этого модуля, потому что вы можете очень хорошо найти решение там.
Обмен приспособлениями с Conftest.py
Если вы пишете много тестов, то в какой-то момент вы поймете, что было бы неплохо иметь все Pteest Светильники в одном месте, из которого вы сможете импортировать их и, следовательно, делиться через тестовые файлы. Это можно решить с Conftest.py
Отказ
Conftest.py
это файл, который находится в базе дерева вашего тестового каталога. В этом файле вы можете хранить все тестовые приборы, и они затем автоматически обнаружены Питиш Так что вам даже не нужно их импортировать.
Это также полезно, если вам нужно делиться данными между несколькими тестами – просто создайте прибор, который возвращает тестовые данные.
Другими полезными функциями являются способность указывать область приспособления – это важно, когда у вас есть приспособления, которые очень дорого создают, например, соединения с базой данных ( сессия
область действия) и на другом конце спектра есть тот, который нужно Чтобы сбросить после каждого тестового корпуса ( Функция
Область применения). Возможные значения для крепления Область
являются: Функция
, класс
, модуль
, Пакет
и сессия
.
Параметрирующие приспособления
Мы уже говорили о приспособлениях в приведенных выше примерах, поэтому давайте пойдем немного глубже. Что, если вы хотите создать немного более общих светильников, параметризуя их?
import pytest, os @pytest.fixture(scope='function') def reset_sqlite_db(request): path = request.param # Path to database file with open(path, 'w'): pass yield None os.remove(path) @pytest.mark.parametrize('reset_sqlite_db', ['/tmp/test_db.sql'], indirect=True) def test_send_message(reset_sqlite_db): ... # Perform tests that access prepared SQLite database
Выше приведен пример крепежа, который готовит базу данных тестирования SQLite для каждого теста. Этот прибор получает путь к базе данных в качестве параметра. Этот путь передан в прибор, используя Запрос
объект, какой атрибут Param
Имеется ли у всех аргументов, переданных к приспособлению, в этом случае только один – путь. Здесь этот прибор сначала создает файл базы данных (и может также заполнить его – опущено для ясности), затем дает выполнение к тесту и после завершения теста, прибор удаляет файл базы данных.
Что касается самого теста, мы используем @ pytest.mark.parametrize.
С 3 аргументами – в первую очередь – это название крепежа, второй – список значений аргумента для крепежа, который станет request.param
И, наконец, ключевое слово аргумент непрямо = Правда
, что вызывает появление ценностей аргумента в request.param.
.
Последнее, что нам нужно сделать, это добавить приспособление в качестве аргумента для тестирования, и мы сделаем.
Пропуск тестов
Есть ситуации, когда разумно пропустить некоторые тесты, будь то из-за окружающей среды ( Linux vs Windows ), подключение к Интернету, доступность ресурсов или других. Итак, как мы это делаем?
import pytest, os @pytest.mark.skipif(os.system("service postgresql status") > 0, reason="PostgreSQL service is not running") def test_connect_to_database(): ... # Run function that tries to connect to PostgreSQL database
Это показывает очень простой пример того, как вы можете пропустить действительный тест на основе некоторого состояния – в этом случае на основе того, работает ли сервер PostgreSQL на машине или нет. Есть много более крутых функций в Питиш связанные с пропуском или ожиданием неудач, и они очень хорошо документированы здесь Итак, я не буду здесь подробно поработать, так как кажется избыточным для копирования и вставки существующего контента здесь.
Заключение
Надеюсь, что эти немногие советы сделают ваш опыт тестирования немного более приятным и эффективным, и поэтому мотивируют вас писать еще несколько тестов, а затем. Если у вас есть какие-либо вопросы, не стесняйтесь добраться до меня, а также если у вас есть какие-либо предложения или трюки, пожалуйста, поделитесь их здесь. 🙂
Оригинал: “https://dev.to/martinheinz/pytest-features-that-you-need-in-your-testing-life-25cd”