Попробуем обобщить то, что мы точно знаем раз выбрали такие примеры:
Функция принимаетlist[int]
Функция возвращаетlist[int] того же содержания
Функция возвращаетlist[int], в котором каждый следующий элемент больше предыдущего
То есть, вместо того, чтобы придумывать кейсы, когда функция должна работать, а на каких может упасть, мы сосредоточились на том, а какими свойствами должен обладать выход функции, но для этого нам нужно определиться с тем, а в какой области эта функция вообще определена.
Теперь осталось добавить просто генерацию входных значений и вы получите, в базовом виде, property based тестирование.
Hypothesis
Установка
В случае установки из PyPI стоит обратить внимание на то, что различные интеграции вынесены в модуль extra, который по умолчанию не устанавливается:
pip install hypothesis[numpy,pandas]
При установке из conda channels вы сразу получите все дополнительные интеграции:
conda install -c conda-forge hypothesis
Создание теста
Рассмотрим на примере выше, с функцией сортировки:
Для создания собственной стратегии используйте декоратор @composite
@compositedef reimplementing_sets_strategy(draw, elements=st.integers(), size=5): result =set()for _ inrange(size): result.add(draw(elements.filter(lambda x: x notin result)))return result
Затем можно использовать эту стратегию, как и любую другую. Она также будет обладать всеми стандартными методами других стратегий, а значит, можно её затем также скомбинировать.
Фиксация кейсов
Если мы хотим, чтобы какой-то кейс постоянно проверялся, мы можем добавить декоратор @example.
from hypothesis import given, exampleimport hypothesis.strategies as st@given(st.lists(st.integers()))@example([1,0,0,0])def test_sort(data): sorted_data = sort(data)for i inrange(len(sorted_data) -1):assert sorted_data[i] <= sorted_data[i+1]
Исключение кейсов
Иногда hypothesis может сгенерировать какой-то кейс, который вам не особо то и важно проверять. Но при этом, на нём ваша функция падает. Конечно, было бы правильно объявить подходящую стратегию и исключить этот случай из рассмотрения таким образом. Однако, иногда это и правда один надоедливый кейс, на который не хочется тратить времени.
Например, возьмем вот такой тест:
from hypothesis import givenimport hypothesis.strategies as st@given(st.floats())def test_negation_is_self_inverse(x):assert x ==-(-x)
Hypothesis “верно” определит, что это не будет работать в случае float('nan'):
Однако, скорее всего, мы и не предполагаем такие входные данные. Поэтому есть простой способ просто наложить ограничение на конкретно этот случай.
from math import isnanfrom hypothesis import given, assumeimport hypothesis.strategies as st@given(st.floats())def test_negation_is_self_inverse_for_non_nan(x): assume(not isnan(x))assert x ==-(-x)
Важно отметить, что стоит быть аккуратными и не исключить слишком много, иначе hypothesis будет сигнализировать вам, что не может найти достаточное количество примеров.
Интеграции
На самом деле про интеграции hypothesis можно записывать несколько отдельных занятий, поэтому я перечислю кратко то, что рекомендую использовать:
Hypothesis работает из коробки с pytest и unuttest.
Hypothesis может использовать схемы Pydantic для генерации примеров через функцию build.
Hypothesis может использовать схемы Pandera так как у них есть метод .strategy().
hypothesis-csv позволяет генерировать csv файлы.
hypothesis-jsonschema даёт возможность использовать JSON schemas для генерации стратегий.
Полезные техники тестирования
Test oracle
Полезная техника, когда вы в качестве значения для сравнения используйте результат уже существующего алгоритма и/или более простой и понятной реализации, но не оптимизированной.
Roundtrip
Эта техника подходит, если тестируемая функция позволяет проводить обратную операцию. Например, так можно одновременно проверять функции кодирования и декодирования. Так как их последовательное применение, должно вернуть ту же строку.
Strategies as EDA
На самом деле, аналитики данных и так занимаются описанием схем данных, и весьма детальным, когда проводят разведочный анализ. В случае, если они помимо текстового отчета зафиксируют описанные схемы, используя те же dataclass’ы или pydantic/pandera/аналогичное. То тестирование можно начать достаточно рано, так как у вас уже будут готовые схемы для генерации стратегий.